Является ли хорошей практикой обнулять указатель после его удаления? - PullRequest
134 голосов
/ 19 декабря 2009

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

В чем проблемы со следующим кодом?

Foo * p = new Foo;
// (use p)
delete p;
p = NULL;

Это было вызвано ответом и комментариями к другому вопросу. Один комментарий от Нейла Баттерворта вызвал несколько голосов:

Установка указателей на NULL после удаления не является универсальной хорошей практикой в ​​C ++. Бывают моменты, когда это полезно, и времена, когда это бессмысленно и может скрывать ошибки.

Есть много обстоятельств, когда это не помогло бы. Но по моему опыту, это не повредит. Кто-нибудь просветите меня.

Ответы [ 18 ]

78 голосов
/ 19 декабря 2009

Установка указателя на 0 (который является «нулевым» в стандартном C ++, определение NULL из C несколько отличается) позволяет избежать сбоев при двойном удалении.

Обратите внимание на следующее:

Foo* foo = 0; // Sets the pointer to 0 (C++ NULL)
delete foo; // Won't do anything

Принимая во внимание:

Foo* foo = new Foo();
delete foo; // Deletes the object
delete foo; // Undefined behavior 

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

Лучше всего не иметь ошибок двойного удаления, но в зависимости от семантики владения и жизненных циклов объектов на практике это может быть трудно достичь. Я предпочитаю замаскированную ошибку двойного удаления, а не UB.

Наконец, в качестве справочного материала по управлению распределением объектов, я предлагаю вам взглянуть на std::unique_ptr для строгого / единичного владения, std::shared_ptr для совместного владения или другой реализации интеллектуального указателя в зависимости от ваших потребностей.

55 голосов
/ 19 декабря 2009

Установка указателей на NULL после того, как вы удалили то, на что оно указывало, определенно не может повредить, но это часто немного помогает при более серьезной проблеме: почему вы используете указатель в первую очередь? Я вижу две типичные причины:

  • Вы просто хотели, чтобы что-то было выделено в куче. В этом случае оборачивать его в объект RAII было бы намного безопаснее и чище. Завершите область видимости объекта RAII, когда он вам больше не нужен. Вот как работает std::vector, и это решает проблему случайного оставления указателей на освобожденной памяти. Нет указателей.
  • Или, возможно, вам нужна сложная семантика совместного владения. Указатель, возвращаемый из new, может не совпадать с указателем, который вызывается delete. Несколько объектов, возможно, использовали объект одновременно. В этом случае предпочтительнее использовать общий указатель или что-то подобное.

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

42 голосов
/ 19 декабря 2009

У меня есть еще лучшая практика: по возможности заканчивайте область видимости переменной!

{
    Foo* pFoo = new Foo;
    // use pFoo
    delete pFoo;
}
28 голосов
/ 19 декабря 2009

Я всегда устанавливаю указатель на NULL (теперь nullptr) после удаления объектов, на которые он указывает.

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

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

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

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

  5. Если вы уже используете RAII, то в вашем коде не так много delete с самого начала, поэтомуаргумент, что дополнительное назначение вызывает беспорядок, не убеждает меня.

  6. Часто при отладке часто удобно видеть нулевое значение, а не устаревший указатель.

  7. Если это все еще вас беспокоит, используйте вместо этого умный указатель или ссылку.

Я также устанавливаю для других типов дескрипторов ресурса значение no-resource, когда ресурсis free'd (который обычно используется только в деструкторе оболочки RAII, написанной для инкапсуляции ресурса).

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

Обновление: Microsoft считает, что это хорошая практика для обеспечения безопасности, и рекомендует эту практику в своих политиках SDL.Очевидно, MSVC ++ 11 автоматически вытеснит удаленный указатель (во многих случаях), если вы скомпилируете с параметром / SDL.

12 голосов
/ 19 декабря 2009

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

В вашем коде проблема, что происходит (используйте p). Например, если где-то у вас есть такой код:

Foo * p2 = p;

затем установка p в NULL очень мало, так как у вас все еще есть беспокойство по поводу указателя p2.

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

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

Если после delete есть больше кода, да. Когда указатель удаляется в конструкторе или в конце метода или функции, №

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

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

3 голосов
/ 19 декабря 2009

Как уже говорили другие, delete ptr; ptr = 0; не приведет к тому, что демоны вылетят из вашего носа. Тем не менее, он поощряет использование ptr в качестве своего рода флага. Код засоряется с delete и установкой указателя на NULL. Следующим шагом является разбрасывание if (arg == NULL) return; по вашему коду для защиты от случайного использования указателя NULL. Эта проблема возникает, когда проверки на NULL становятся вашими основными средствами проверки состояния объекта или программы.

Я уверен, что в коде есть указатель на использование указателя в качестве флага, но я его не нашел.

2 голосов
/ 19 декабря 2009

Если у вас нет другого ограничения, которое заставляет вас устанавливать или не устанавливать указатель в NULL после его удаления (одно такое ограничение было упомянуто Нилом Баттервортом ), тогда я лично предпочитаю оставить это будет.

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

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

2 голосов
/ 19 декабря 2009

Я немного изменю ваш вопрос:

Вы бы использовали неинициализированный указатель? Вы знаете, тот, который вы не сделали установить в NULL или выделить память указывает на?

Существует два сценария, в которых установка указателя на NULL может быть пропущена:

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

Между тем, утверждение, что установка указателя на NULL может скрывать ошибки для меня, звучит как утверждение, что вы не должны исправлять ошибку, потому что исправление может скрывать другую ошибку. Единственные ошибки, которые могут отображаться, если указатель не установлен в NULL, будут те, которые пытаются использовать указатель. Но установка его в NULL фактически вызовет точно такую ​​же ошибку, как если бы вы использовали его с освобожденной памятью, не так ли?

2 голосов
/ 19 декабря 2009

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

Если это то, что вы на самом деле имеете в виду, лучше сделать это явно в источнике, используя что-то вроде boost :: необязательно

optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();

Но если вы действительно хотите, чтобы люди знали, что указатель «испортился», я согласен на 100% согласиться с теми, кто говорит, что лучше всего сделать так, чтобы он вышел из области видимости. Затем вы используете компилятор для предотвращения возможности неправильных разыменований во время выполнения.

Это ребенок во всей воде С ++, не должен выбрасывать его. :)

...