Установка переменной в NULL после освобождения - PullRequest
141 голосов
/ 22 июня 2009

В моей компании есть правило кодирования, которое гласит, что после освобождения памяти сбросьте переменную в NULL. Например ...

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

Мне кажется, что в случаях, подобных приведенному выше, установка значения NULL не имеет никакого значения. Или я что-то упустил?

Если в таких случаях нет смысла, я собираюсь обсудить это с «командой качества», чтобы удалить это правило кодирования. Пожалуйста, совет.

Ответы [ 23 ]

273 голосов
/ 22 июня 2009

Установка неиспользуемых указателей в NULL - это защитный стиль, защищающий от ошибок висячих указателей. Если после освобождения доступен свисающий указатель, вы можете прочитать или перезаписать случайную память. При обращении к нулевому указателю на большинстве систем происходит немедленный сбой, сразу же сообщая, в чем заключается ошибка.

Для локальных переменных может быть немного бессмысленно, если «очевидно», что указатель больше не доступен после освобождения, поэтому этот стиль больше подходит для данных-членов и глобальных переменных. Даже для локальных переменных это может быть хорошим подходом, если функция продолжается после освобождения памяти.

Чтобы завершить стиль, вы должны также инициализировать указатели в NULL, прежде чем им будет присвоено истинное значение указателя.

36 голосов
/ 10 декабря 2009

Установка указателя на NULL после free - сомнительная практика, которая часто популяризируется как правило «хорошего программирования» на явно ложной предпосылке. Это одна из тех фальшивых истин, которые относятся к категории «звучит правильно», но в действительности не достигают абсолютно ничего полезного (а иногда приводят к негативным последствиям).

Предположительно, установка указателя на NULL после free должна предотвратить страшную проблему "двойного освобождения", когда одно и то же значение указателя передается в free более одного раза. В действительности, однако, в 9 случаях из 10 реальная проблема «двойного освобождения» возникает, когда разные объекты указателя, содержащие одно и то же значение указателя, используются в качестве аргументов для free. Излишне говорить, что установка указателя на NULL после free абсолютно ничего не дает для предотвращения проблемы в таких случаях.

Конечно, можно столкнуться с проблемой "двойного освобождения" при использовании того же объекта указателя в качестве аргумента free. Однако в реальности подобные ситуации обычно указывают на проблему с общей логической структурой кода, а не на случайное «двойное освобождение». Надлежащим способом решения проблемы в таких случаях является пересмотр и переосмысление структуры кода, чтобы избежать ситуации, когда один и тот же указатель передается в free более одного раза. В таких случаях установка указателя на NULL и рассмотрение проблемы как «исправленной» - не более чем попытка скрыть проблему. Это просто не будет работать в общем случае, потому что проблема со структурой кода всегда найдет другой способ проявить себя.

Наконец, если ваш код специально разработан для использования значения указателя NULL или не NULL, то прекрасно установить значение указателя на NULL после free. Но в качестве общего правила «хорошей практики» (как в «всегда устанавливайте указатель на NULL после free»), это, опять же, хорошо известная и довольно бесполезная подделка, за которой часто следуют некоторые из них для чисто религиозных, Вуду-подобные причины.

31 голосов
/ 10 декабря 2009

Большинство ответов были направлены на предотвращение двойного освобождения, но установка указателя на NULL имеет еще одно преимущество. Как только вы освободите указатель, эта память станет доступной для перераспределения другим вызовом malloc. Если у вас все еще есть оригинальный указатель, вы можете получить ошибку, когда вы попытаетесь использовать указатель после освобождения и испортить какую-то другую переменную, а затем ваша программа войдет в неизвестное состояние, и могут произойти все виды плохих вещей (сбой, если повезло, повреждение данных, если вам не повезло). Если вы установили указатель на NULL после освобождения, любая попытка чтения / записи через этот указатель позже приведет к segfault, что обычно предпочтительнее случайного повреждения памяти.

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

18 голосов
/ 22 июня 2009

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

Попробуйте что-то вроде этого:

#if DEBUG_VERSION
void myfree(void **ptr)
{
    free(*ptr);
    *ptr = NULL;
}
#else
#define myfree(p) do { void ** __p = (p); free(*(__p)); *(__p) = NULL; } while (0)
#endif

DEBUG_VERSION позволяет вам профилировать освобождения в отладкекод, но оба они функционально одинаковы.

Редактировать : Добавлено do ... в то время, как указано ниже, спасибо.

7 голосов
/ 22 июня 2009

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

Если вы установите указатель на NULL, то при обращении к нему программа всегда завершается сбоем с ошибкой. Не более ,, иногда это работает '', не более ,, сбой непредсказуемым образом ''. Это намного проще для отладки.

7 голосов
/ 22 июня 2009

Установка указателя на память free означает, что любая попытка получить доступ к этой памяти через указатель немедленно приведет к краху, вместо того, чтобы вызвать неопределенное поведение. Намного легче определить, где что-то пошло не так.

Я вижу ваш аргумент: поскольку nPtr выходит из области видимости сразу после nPtr = NULL, похоже, нет причины устанавливать его на NULL. Однако, в случае элемента struct или где-либо еще, где указатель не сразу выходит из области видимости, это имеет больше смысла. Не сразу очевидно, будет ли этот указатель снова использоваться кодом, который не должен его использовать.

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

7 голосов
/ 10 декабря 2009

самая распространенная ошибка в c - это двойное освобождение. В основном вы делаете что-то подобное

free(foobar);
/* lot of code */
free(foobar);

и это в конечном итоге довольно плохо, ОС пытается освободить часть уже освобожденной памяти и, как правило, это segfault. Поэтому рекомендуется установить значение NULL, чтобы вы могли выполнить тестирование и проверить, действительно ли вам нужно освободить эту память

if(foobar != null){
  free(foobar);
}

также следует отметить, что free(NULL) ничего не сделает, поэтому вам не нужно писать оператор if. Я на самом деле не гуру ОС, но я симпатичен даже сейчас, когда большинство ОС вылетает при двойном освобождении.

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

6 голосов
/ 22 июня 2009

Идея заключается в том, чтобы остановить случайное повторное использование освобожденного указателя.

4 голосов
/ 09 октября 2012

Недавно я столкнулся с тем же вопросом после того, как искал ответ. Я пришел к такому выводу:

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

free() - это библиотечная функция, которая изменяется при изменении платформы, поэтому не следует ожидать, что после передачи указателя на эту функцию и после освобождения памяти этот указатель будет установлен в значение NULL. Это может не относиться к некоторым библиотекам, реализованным для платформы.

так всегда идите на

free(ptr);
ptr = NULL;
4 голосов
/ 10 декабря 2009

Я считаю, что это мало помогает, так как по моему опыту, когда люди получают доступ к свободному распределению памяти, это почти всегда, потому что у них где-то есть другой указатель. А затем он вступает в противоречие с другим стандартом личного кодирования, который называется «Избегайте бесполезных помех», поэтому я этого не делаю, так как считаю, что это редко помогает и делает код немного менее читабельным.

Однако - я не буду устанавливать переменную в null, если указатель не предполагается использовать снова, но часто дизайн более высокого уровня дает мне причину установить его в любом случае в null. Например, если указатель является членом класса, и я удалил то, на что он указывает, тогда «контракт», если вам нравится класс, заключается в том, что этот член будет указывать на что-то допустимое в любое время, поэтому для него должно быть установлено значение null. по этой причине. Небольшое различие, но я считаю важным.

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

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

Подводя итог, да, я часто устанавливаю указатель на NULL после удаления чего-либо, но это как часть более крупного проекта и размышлений о том, кто владеет данными, а не из-за слепого следования стандартному правилу кодирования. Я бы не стал этого делать в вашем примере, так как считаю, что это бесполезно, и он добавляет «беспорядок», который, по моему опыту, столь же ответственен за ошибки и плохой код, что и подобные вещи.

...