Вопрос

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

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

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

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

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

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

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

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

Вместо этого попробуйте что-то вроде этого:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

void free(void *ptr);
  

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

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

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

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

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

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

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

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

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

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

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

поэтому всегда выбирайте

free(ptr);
ptr = NULL;

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

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

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

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

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

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

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


if(ptr)
   ptr->CallSomeMethod();

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

Это может быть больше аргумент для инициализации всех указателей в 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 может перезаписать все виды несвязанных вещей и привести к ужасным ошибкам. Особенно, если компилятор инициализирует локальные переменные с нулем в режиме отладки, но не включает оптимизацию. Таким образом, отладочные сборки не показывают никаких признаков ошибки, в то время как сборки выпуска взрываются случайным образом ...

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

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

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

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

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

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

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

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

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

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

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

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

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

  

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

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

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

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

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

     

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

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

Автор Martin v. L & # 246; wis в другой ответ .

  

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

     

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

     

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

Поскольку у вас есть команда по обеспечению качества, позвольте мне добавить небольшое замечание о QA. Некоторые автоматизированные инструменты QA для C помечают назначения для освобожденных указателей как «бесполезное назначение для ptr ». Например, PC-lint / FlexeLint от Gimpel Software сообщает tst.c 8 Предупреждение 438: последнее значение, присвоенное переменной 'nPtr' (определено в строке 5), не используется

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

Всегда желательно объявлять переменную-указатель с помощью НУЛЕВОЙ такой как,

int *ptr = NULL;

Скажем, ПТР указывает на 0x1000 адрес памяти.После использования free(ptr), всегда желательно обнулить переменную-указатель, снова объявив НУЛЕВОЙ.например.:

free(ptr);
ptr = NULL;

Если не будет повторно объявлено НУЛЕВОЙ, переменная-указатель продолжает указывать на тот же адрес (0x1000), эта переменная-указатель называется висячий указатель.Если вы определите другую переменную-указатель (скажем, д) и динамически выделять адрес новому указателю, есть вероятность получить тот же адрес (0x1000) по новой переменной указателя.Если в этом случае вы используете тот же указатель (ПТР) и обновить значение по адресу, указанному тем же указателем (ПТР), то программа в конечном итоге запишет значение в то место, где д указывает (поскольку п и д указывают на один и тот же адрес (0x1000)).

например

*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.

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top