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


156

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

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

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

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


3
всегда полезно иметь возможность проверить, ptr == NULLпрежде чем что-либо делать с ним. Если вы не аннулируете свои свободные указатели, вы получите, ptr != NULLно все еще неиспользуемый указатель.
Ки Жей

Свисающие указатели могут привести к уязвимостям, таким как Use-After-Free .
Константин Ван

Ответы:


285

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

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

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


3
Я не понимаю, почему вы должны "инициализировать указатели в NULL, прежде чем они получат истинное значение указателя"?
Пол Биггар

26
@Paul: В конкретном случае объявление можно прочитать int *nPtr=NULL;. Теперь, я бы согласился, что это было бы излишним, с malloc, следующим прямо в следующей строке. Однако, если есть код между объявлением и первой инициализацией, кто-то может начать использовать переменную, даже если она еще не имеет значения. Если вы инициализируете null, вы получите segfault; без, вы могли бы снова читать или писать случайную память. Аналогично, если переменная впоследствии инициализируется только условно, последующие ошибочные обращения должны дать вам мгновенные сбои, если вы вспомнили об инициализации null.
Мартин против Лёвиса

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

9
Вильгельм, суть в том, что при разыменовании нулевого указателя вы получаете определенный сбой и фактическое местонахождение проблемы. Неправильный доступ может привести к сбою, а может и не привести к повреждению данных или поведению неожиданным образом в неожиданных местах.
Амит Найду

4
На самом деле инициализация указателя на NULL имеет как минимум один существенный недостаток: он может помешать компилятору предупреждать вас о неинициализированных переменных. Если логика вашего кода фактически не обрабатывает это значение для указателя (т. Е. Если (nPtr == NULL) что-то изменится ...), лучше оставить все как есть.
Эрик

37

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

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

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

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


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

4
@ А "сомнительно" немного. Все зависит от варианта использования. Если значение указателя когда-либо используется в истинном / ложном смысле, это не только правильная практика, но и лучшая практика.
Кодер

1
@ Кодер Совершенно неправильно. Если значение указателя используется в истинном ложном смысле, чтобы узнать, указывал ли он на объект перед вызовом free, это не только не лучшая практика, это неправильно . Например: foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;. Здесь, установив barдля NULLпосле вызова freeбудет вызывать функцию думать , что никогда не было бара и вернуть неправильное значение!
Дэвид Шварц

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

1
Я согласен, что хорошо структурированный код не должен допускать случай, когда указатель доступен после освобождения, или случай, когда он освобождается два раза. Но в реальном мире мой код будет изменен и / или поддерживаться кем-то, кто, вероятно, не знает меня и не имеет времени и / или навыков, чтобы делать что-то правильно (потому что крайний срок всегда вчера). Поэтому я склонен писать пуленепробиваемые функции, которые не приводят к сбою системы даже при неправильном использовании.
Мфлорис

35

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

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


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

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

1
@DavidSchwartz: я не согласен с вашим комментарием. Когда несколько недель назад мне пришлось написать стек для университетского упражнения, у меня возникла проблема, я исследовал несколько часов. В какой-то момент я получил доступ к уже свободной памяти (некоторые строки были слишком ранними). И иногда это приводит к очень странному поведению. Если бы после освобождения я установил бы указатель на NULL, был бы «простой» segfault, и я бы сэкономил пару часов работы. Так что +1 за этот ответ!
mozzbozz

2
@katze_sonne Даже остановленные часы работают два раза в день. Гораздо более вероятно, что установка указателей на NULL будет скрывать ошибки, предотвращая ошибочный доступ к уже освобожденным объектам от segfaulting в коде, который проверяет NULL, а затем молча не в состоянии проверять объект, который он должен был проверить. (Возможно, было бы полезно установить указатели на NULL после освобождения в определенных сборках отладки или установить для них значение, отличное от NULL, которое гарантированно будет иметь значение segfault. Но то, что эта глупость помогла вам однажды, не является аргументом в ее пользу .)
Дэвид Шварц

@DavidSchwartz Ну, это звучит разумно ... Спасибо за ваш комментарий, я рассмотрю это в будущем! :) +1
mozzbozz

20

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

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

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

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

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


3
Макро версия имеет небольшую ошибку, если вы используете ее после оператора if без скобок.
Марк Рэнсом

Что с (пустота) 0? Этот код делает: if (x) myfree (& x); еще do_foo (); становится if (x) {free (* (& x)); * (& x) = ноль; } void 0; еще do_foo (); Остальное ошибка.
jmucchiello

Этот макрос является идеальным местом для оператора запятой: free ( (p)), * (p) = null. Конечно, следующая проблема состоит в том, что он оценивает * (p) дважды. Это должно быть {void * _pp = (p); бесплатно (* _ с); * _pp = null; } Разве препроцессор не веселый.
jmucchiello

5
Макрос не должен быть в квадратных скобках, он должен быть в do { } while(0)блоке, чтобы if(x) myfree(x); else dostuff();не разрываться.
Крис Латс

3
Как сказал Лутц, макро-тело do {X} while (0)- это IMO - лучший способ создать макро-тело, которое «чувствует и работает как» функция. В любом случае, большинство компиляторов оптимизируют цикл.
Майк Кларк

7

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

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


5
Программа не всегда вылетает с ошибкой. Если способ обращения к указателю означает, что перед разыменованием к нему применяется достаточно большое смещение, тогда он может достигнуть адресуемой памяти: ((MyHugeStruct *) 0) -> fieldNearTheEnd. И это еще до того, как вы столкнетесь с оборудованием, которое вообще не работает с нулевым доступом. Тем не менее, программа с большей вероятностью вылетит из-за ошибки.
Стив Джессоп

7

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

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

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


7

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

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

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

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

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

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


11
На самом деле вы можете просто вызвать free () без проверки - free (NULL) определяется как ничего не делать.
Январь

5
Разве это не скрывает ошибки? (Как освобождение слишком много.)
Георг Шолли

1
спасибо, я понял. я попробовал:p = (char *)malloc(.....); free(p); if(p!=null) //p!=null is true, p is not null although freed { free(p); //Note: checking doesnot prevent error here }
Shaobo Wang

5
Как я уже сказал, free(void *ptr) не может изменить значение указателя это передается. Он может изменить содержимое указателя, данные, хранящиеся по этому адресу , но не сам адрес или значение указателя . Для этого потребуется free(void **ptr)(что, очевидно, не разрешено стандартом) или макрос (который разрешен и идеально переносим, ​​но людям не нравятся макросы). Кроме того, C не об удобстве, а о предоставлении программистам столько контроля, сколько они хотят. Если они не хотят дополнительных накладных расходов на установку указателей NULL, их не следует навязывать им.
Крис Латс

2
В мире есть немного вещей, которые бы указывали на недостаток профессионализма со стороны автора кода на C. Но они включают «проверку указателя на NULL перед вызовом free» (наряду с такими вещами, как «приведение результата функций выделения памяти» или «бездумное использование имен типов с sizeof»).
AnT

6

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


4

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

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


По крайней мере, в Windows отладочные сборки устанавливают в память значение 0xdddddddd, поэтому, когда вы используете указатель на удаленную память, вы сразу это знаете. Должны быть похожие механизмы на всех платформах.
i_am_jorf

2
jeffamaphone, удаленный блок памяти мог быть перераспределен и назначен другому объекту к тому времени, когда вы снова используете указатель.
Константин

4

Из стандарта 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;
}

@DrPizza - Ошибка (на мой взгляд) - это то, что заставляет вашу программу работать не так, как она должна. Если скрытое двойное освобождение нарушает вашу программу, это ошибка. Если это работает именно так, как было задумано, это не ошибка.
Крис Латс

@DrPizza: Я только что нашел аргумент, почему следует установить его, NULLчтобы избежать ошибок маскировки. stackoverflow.com/questions/1025589/… Кажется, что в любом случае некоторые ошибки будут скрыты.
Георг Шолли

1
Имейте в виду,
Безопасный

3
@ Крис, нет, лучший подход - это структура кода. Не выбрасывайте случайные malloc и освобождайте всюду по своей кодовой базе, держите связанные вещи вместе. «Модуль», который выделяет ресурс (память, файл, ...), отвечает за его освобождение и должен предоставлять функцию для этого, которая также заботится о указателях. Для любого конкретного ресурса у вас есть ровно одно место, где он размещен, и одно место, где он выпущен, оба близко друг к другу.
Безопасный

4
@ Крис Лутц: Хогваш. Если вы пишете код, который дважды освобождает один и тот же указатель, ваша программа содержит логическую ошибку. Маскировка этой логической ошибки, не приводя к сбою, не означает, что программа правильная: она все еще делает что-то бессмысленное. Не существует сценария, при котором написание двойного свободного оправдано.
DrPizza

4

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

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

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

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

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


4

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

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

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

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

free(ptr);
ptr = NULL;

3

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

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

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

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

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

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


2

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


if(ptr)
   ptr->CallSomeMethod();

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


5
Во многих случаях, когда указатель NULL не имеет смысла, предпочтительнее вместо этого написать утверждение.
Эрих Кицмюллер

2

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

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


2

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

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

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


2

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

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


2

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

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

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

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


Хороший ответ. Я хотел бы добавить одну вещь. Проверка базы данных ошибок полезна по ряду причин. Но в контексте исходного вопроса имейте в виду, что было бы трудно узнать, сколько проблем с недопустимыми указателями было предотвращено или, по крайней мере, выявлено так рано, что они не попали в базу данных ошибок. История ошибок предоставляет лучшее доказательство добавления правил кодирования.
Jimhark

2

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

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

Написано RageZ в двойном вопросе .

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

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

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

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

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

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

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

Написано Мартином против Лёвиса в другом ответе .

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

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

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


1

Поскольку у вас есть команда по обеспечению качества, позвольте мне добавить небольшое замечание о 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

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

int *ptr = NULL;

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

free(ptr);
ptr = NULL;

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

например

*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

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.