it-swarm.com.ru

Установка переменной в NULL после освобождения

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

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

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

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

141
Alphaneo

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

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

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

273
Martin v. Löwis

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

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

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

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

36
AnT

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

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

31
Mike McNertney

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

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

#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 позволяет вам профилировать освобождения в коде отладки, но оба они функционально одинаковы.

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

18
razzed

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

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

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

7
Jared Oberhaus

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

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

7
Tadeusz A. Kadłubowski

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

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

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

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

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

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

7
RageZ

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

6
Mitch Wheat

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

Если вы НЕ УСТАНАВЛИВАЕТЕ указатель, то любая попытка его использования будет разыменовываться 0x0 и сразу же завершаться сбоем, что легко отладить. Случайные указатели, указывающие на случайную память, трудно отлаживать. Очевидно, что в этом нет необходимости, но именно поэтому он содержится в документе с рекомендациями.

4
Steven Canfield

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

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

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

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

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

4
jcoder

Из стандарта ANSI C:

void free(void *ptr);

Свободная функция вызывает освобождение пространства, на которое указывает ptr, то есть делает его доступным для дальнейшего выделения. Если ptr является нулевым указателем, никаких действий не происходит. В противном случае, если аргумент не совпадает с указателем, ранее возвращенным функцией calloc, malloc или realloc, или если пространство было освобождено при вызове free или realloc, поведение не определено.

"неопределенное поведение" - это почти всегда сбой программы. Чтобы избежать этого, можно безопасно сбросить указатель на NULL. Сам по себе free () не может этого сделать, поскольку ему передается только указатель, а не указатель на указатель. Вы также можете написать более безопасную версию free () с нулевым указателем:

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}
4
Vijay Mathew

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

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

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

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

free(ptr);
ptr = NULL;
4
Jalindar

Это правило полезно, когда вы пытаетесь избежать следующих сценариев:

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

2) Указатель является переменной-членом класса, которая имеет довольно сложное поведение, и вы не хотите случайно повторно использовать указатель на удаленную память в других функциях.

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

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

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

3
i_am_jorf

Есть две причины:

Избегайте сбоев при двойном освобождении

Автор RageZ в повторяющийся вопрос .

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

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

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

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

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

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

Избегайте использования уже освобожденных указателей

Автор: Martin v. Löwis в другой ответ .

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

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

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

2
Georg Schölly

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

Но будь осторожен. Не все системы вызывают segfault, если вы разыменовываете NULL. В (по крайней мере, в некоторых версиях) AIX, * (int *) 0 == 0, и Solaris имеет дополнительную совместимость с этой "функцией" AIX.

2
Jaap Weel

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


if(ptr)
   ptr->CallSomeMethod();

Явная маркировка указателя как NULL после освобождения позволяет использовать этот тип в C/C++.

2
Aamir

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

void other_func() {
  int *p; // forgot to initialize
  // some unrelated mallocs and stuff
  // ...
  if (p) {
    *p = 1; // hm...
  }
}

void caller() {
  some_func();
  other_func();
}

p заканчивается в том же месте в стеке, что и прежнее nPtr, поэтому он все еще может содержать, казалось бы, действительный указатель. Назначение *p может перезаписать все виды несвязанных вещей и привести к ужасным ошибкам. Особенно, если компилятор инициализирует локальные переменные с нулем в режиме отладки, но не включает оптимизацию. Таким образом, отладочные сборки не показывают никаких признаков ошибки, в то время как сборки выпуска взрываются случайным образом ...

2
sth

Установить только что освобожденный указатель на NULL не обязательно, но это хорошая практика. Таким образом, вы можете избежать 1) использовать освобожденный остроконечный 2) освободить его

2
pierrotlefou

Установка указателя на NULL для защиты от так называемого двойного освобождения - ситуация, когда free () вызывается более одного раза для одного и того же адреса без перераспределения блока по этому адресу.

Двойное освобождение приводит к неопределенному поведению - обычно кучи повреждения или немедленного сбоя программы. Вызов free () для указателя NULL ничего не делает и поэтому гарантированно безопасен.

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

2
sharptooth

Исходный вопрос: установка указателя на NULL сразу после освобождения содержимого - это пустая трата времени, при условии, что код соответствует всем требованиям, полностью отлажен и никогда не будет изменен. С другой стороны, защита NULL от освобожденного указателя может быть весьма полезна, когда кто-то бездумно добавляет новый блок кода под free (), когда дизайн исходного модуля неверен, и в случае этого -компилирует, но не делает то, что я хочу ошибки.

В любой системе есть недостижимая цель - сделать ее проще и правильнее, а также снизить стоимость неточных измерений. В Си мы предлагаем набор очень острых, очень сильных инструментов, которые могут создать много вещей в руках квалифицированного рабочего и нанести всевозможные метафорические травмы при неправильном обращении. Некоторые из них трудно понять или использовать правильно. И люди, естественно, склонные к риску, делают иррациональные вещи, такие как проверка указателя на значение NULL перед тем, как вызвать его бесплатно…

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

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

2
Bill IV

Всегда желательно объявлять переменную-указатель с NULL, например,

int *ptr = NULL;

Допустим, ptr указывает на x10 адрес памяти. После использования free(ptr) всегда желательно обнулить переменную-указатель, объявив снова NULL. например.:

free(ptr);
ptr = NULL;

Если не объявлено повторно в NULL, переменная-указатель продолжает указывать на тот же адрес (x10), эта переменная-указатель называется висячий указатель. Если вы определяете другую переменную-указатель (скажем, q) и динамически выделяете адрес для нового указателя, есть шанс получить тот же адрес (x10) новым указателем переменная. Если в этом случае вы используете один и тот же указатель (ptr) и обновите значение по адресу, на который указывает тот же указатель (ptr), то программа завершит запись значение места, куда указывает q (поскольку p и q указывают на один и тот же адрес (x10) ).

например.

*ptr = 20; //Points to 0x1000
free(ptr);
int *q = (int *)malloc(sizeof(int) * 2); //Points to 0x1000
*ptr = 30; //Since ptr and q are pointing to the same address, so the value of the address to which q is pointing would also change.
1
Pankaj Kumar Thapa

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

Однако, если вы не установите указатель на NULL и по ошибке попытаетесь отменить ссылку на указатель или изменить значение этого адреса; ВЫ МОЖЕТЕ ЕЩЕ ДЕЛАТЬ. НО ЧТО-ТО, ЧТО ВЫ ЛОГИЧЕСКИ ХОТИТЕ СДЕЛАТЬ.

Почему я все еще могу получить доступ к памяти, которую я освободил? Потому что: возможно, вы освободили память, но переменная-указатель все еще содержала информацию об адресе кучи памяти. Итак, в качестве защитной стратегии, пожалуйста, установите ее в NULL.

1
Ehsan

Поскольку у вас есть команда по обеспечению качества, позвольте мне добавить небольшое замечание о QA. Некоторые автоматизированные инструменты QA для C помечают назначения для освобожденных указателей как "бесполезное назначение ptr". Например, PC-lint/FlexeLint от Gimpel Software сообщает tst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used

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

1
Jens