Подтвердить что ты не робот

Строки С++ - Как избежать получения недопустимого указателя?

В нашем коде на С++ у нас есть собственный класс строк (по старым причинам). Он поддерживает метод c_str(), похожий на std::string. Я заметил, что многие разработчики используют его неправильно. Я уменьшил проблему до следующей строки:

 const char* x = std::string("abc").c_str();

Этот, казалось бы, невинный код довольно опасен в том смысле, что деструктор на std::string вызывается сразу после вызова c_str(). В результате вы удерживаете указатель на выделенную ячейку памяти.

Вот еще один пример:

  std::string x("abc");
  const char* y = x.substr(0,1).c_str();

Здесь также мы используем указатель на выделенное местоположение.

Эти проблемы нелегко найти во время тестирования, поскольку в памяти по-прежнему содержатся достоверные данные (хотя сама ячейка памяти недействительна).

Мне интересно, есть ли у вас какие-либо предложения относительно того, как я могу изменить определение класса/метода, чтобы разработчики никогда не могли совершить такую ​​ошибку.

4b9b3361

Ответ 1

Современная часть кода не должна обрабатывать необработанные указатели. Вызовите c_str только при предоставлении аргумента устаревшей функции, которая принимает const char*. Как:

legacy_print(x.substr(0,1).c_str())

Почему вы хотите создать локальную переменную типа const char*? Даже если вы пишете версию для копирования c_str_copy(), вы просто получите больше головной боли, потому что теперь клиентский код отвечает за удаление результирующего указателя.

И если вам нужно поддерживать данные в течение более длительного времени (например, потому что вы хотите передать данные нескольким унаследованным функциям), просто сохраняйте данные, завернутые в экземпляр строки все время.

Ответ 2

В базовом случае вы можете добавить спецификатор ref в объекте "this", чтобы убедиться, что .c_str() никогда не вызывается мгновенно. Конечно, это не может помешать им хранить в переменной, которая оставляет область до того, как будет сделан указатель.

const char *c_str() & { return ...; }

Но решение большего размера заключается в замене всех функций на "const char *" в вашей кодовой базе с помощью функций, которые берут один из ваших строковых классов (по крайней мере, вам нужно два: владеющая строка и заимствованный фрагмент) - и убедитесь, что ни один из ваших строковых классов не может быть неявно построен из "const char *".

Ответ 3

Простейшим решением было бы изменить ваш деструктор, чтобы записать нуль в начале строки во время уничтожения. (В качестве альтернативы, заполните всю строку сообщением об ошибке или 0, вы можете иметь флаг, чтобы отключить это для кода выпуска.)

В то время как это не препятствует программистам совершать ошибки при использовании недействительных указателей, это определенно привлечет внимание к проблеме, когда код не сделает то, что он должен делать. Это должно помочь вам устранить проблему в вашем коде.

(Как вы уже упоминали, в настоящий момент ошибки остаются незамеченными, потому что по большей части код будет успешно работать с недопустимой памятью.)

Ответ 4

Рассмотрите возможность использования Valgrind или Electric Fence для проверки вашего кода. Любой из этих инструментов должен тривиально и сразу найти эти ошибки.

Ответ 5

Я не уверен, что вы можете очень многого сделать с людьми, использующими вашу библиотеку, если вы их предупреждаете. Рассмотрим фактическую библиотеку stl string. Если я сделаю это:

const char * lala = std::string("lala").c_str();
std::cout << lala << std::endl;
const char * lala2 = std::string("lalb").c_str();
std::cout << lala << std::endl;
std::cout << lala2 << std::endl;

Я в основном создаю поведение undefined. В случае, когда я запускаю его на ideone.com, я получаю следующий вывод:

lala
lalb
lalb

Таким образом, память исходного lala была перезаписана. Я бы просто очень четко дал понять пользователю в документации, что такой вид кодирования является плохой практикой.

Ответ 6

Вы можете удалить функцию c_str() и вместо этого предоставить функцию, которая принимает ссылку на уже созданный пустой интеллектуальный указатель, который сбрасывает значение интеллектуального указателя на новую копию строки. Это заставит пользователя создать не временный объект, который они могли бы использовать для получения исходной строки c, и он будет разрушен и освободится память при выходе из области метода.

Это предполагает, что ваша библиотека и ее пользователи будут использовать одну и ту же кучу.

ИЗМЕНИТЬ

Еще лучше, создайте для этого свой собственный класс интеллектуальных указателей, деструктор которого вызывает библиотечную функцию в вашей библиотеке, чтобы освободить память, чтобы ее можно было использовать через границы DLL.