Есть ли причина проверять NULL-указатель перед удалением?

StackOverflow https://stackoverflow.com/questions/615355

Вопрос

Я часто вижу проверку устаревшего кода на наличие NULL перед удалением указателя, аналогично,

if (NULL != pSomeObject) 
{
    delete pSomeObject;
    pSomeObject = NULL;
}

Есть ли смысл проверять наличие NULL указатель перед его удалением?В чем причина установки указателя на NULL после?

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

Решение

Это совершенно "безопасно" удалить нулевой указатель; это фактически равносильно неработоспособности.

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

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

Стандарт C++ гарантирует, что использование нулевого указателя в коде допустимо. выражение-удаление (§8.5.2.5/2).Однако это неопределенные будет ли это вызывать функцию освобождения (operator delete или operator delete[];§8.5.2.5/7, примечание).

Если функция освобождения по умолчанию (т.предоставляемый стандартной библиотекой) вызывается с нулевым указателем, то вызов не имеет никакого эффекта (§6.6.4.4.2/3).

Но неизвестно, что произойдет, если функция освобождения не предусмотрена стандартной библиотекой, т. е.что происходит, когда мы перегружаем operator delete (или operator delete[]).

Компетентный программист будет обрабатывать нулевые указатели соответствующим образом. внутри функцию освобождения, а не перед вызовом, как показано в коде OP. Аналогично, установка указателя на nullptr/NULL после удаления служит лишь очень ограниченной цели.Некоторым людям нравится делать это в духе защитное программирование:это сделает поведение программы более предсказуемым в случае ошибки:доступ к указателю после удаления приведет к доступу к нулевому указателю, а не к доступу к произвольной ячейке памяти.Хотя обе операции имеют неопределенное поведение, на практике поведение доступа к нулевому указателю гораздо более предсказуемо (чаще всего это приводит к прямому сбою, а не к повреждению памяти).Поскольку повреждения памяти особенно трудно отладить, сброс удаленных указателей помогает в отладке.

— Конечно, это лечение симптома, а не причины (т.баг). Вы должны относиться к сбросу указателей как к запаху кода. Чистый, современный код C++ сделает владение памятью ясным и статически проверяемым (с помощью интеллектуальных указателей или эквивалентных механизмов) и, таким образом, позволит избежать такой ситуации.

Бонус:Объяснение перегрузки operator delete:

operator delete (несмотря на название) — это функция, которую можно перегружать, как и любую другую функцию.Эта функция вызывается внутренне при каждом вызове operator delete с соответствующими аргументами.То же самое верно для operator new.

Перегрузка operator new (а потом еще operator delete) имеет смысл в некоторых ситуациях, когда вы хотите точно контролировать распределение памяти.Сделать это не так уж сложно, но необходимо принять некоторые меры предосторожности, чтобы обеспечить правильное поведение.Скотт Мейерс описывает это очень подробно. Эффективный С++.

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

klass* pobj = new klass;
// … use pobj.
delete pobj;

Что на самом деле здесь происходит?Что ж, вышеизложенное можно примерно перевести в следующий код:

// 1st step: allocate memory
klass* pobj = static_cast<klass*>(operator new(sizeof(klass)));
// 2nd step: construct object in that memory, using placement new:
new (pobj) klass();

// … use pobj.

// 3rd step: call destructor on pobj:
pobj->~klass();
// 4th step: free memory
operator delete(pobj);

Обратите внимание на шаг 2, где мы вызываем new с немного странным синтаксисом.Это призыв к так называемому размещение new который принимает адрес и создает объект по этому адресу.Этот оператор также может быть перегружен.В данном случае достаточно просто вызвать конструктор класса klass.

Теперь, без лишних слов, вот код перегруженной версии операторов:

void* operator new(size_t size) {
    // See Effective C++, Item 8 for an explanation.
    if (size == 0)
        size = 1;

    cerr << "Allocating " << size << " bytes of memory:";

    while (true) {
        void* ret = custom_malloc(size);

        if (ret != 0) {
            cerr << " @ " << ret << endl;
            return ret;
        }

        // Retrieve and call new handler, if available.
        new_handler handler = set_new_handler(0);
        set_new_handler(handler);

        if (handler == 0)
            throw bad_alloc();
        else
            (*handler)();
    }
}

void operator delete(void* p) {
    cerr << "Freeing pointer @ " << p << "." << endl;
    custom_free(p);
}

Этот код просто использует специальную реализацию malloc/free внутри, как и большинство реализаций.Он также создает отладочный вывод.Рассмотрим следующий код:

int main() {
    int* pi = new int(42);
    cout << *pi << endl;
    delete pi;
}

Это дало следующий результат:

Allocating 4 bytes of memory: @ 0x100160
42
Freeing pointer @ 0x100160.

Теперь этот код делает нечто фундаментально отличное от стандартной реализации operator delete: Он не проверял наличие нулевых указателей! Компилятор не проверяет это, поэтому приведенный выше код компилируется, но он может выдавать неприятные ошибки во время выполнения, когда вы пытаетесь удалить нулевые указатели.

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

void operator delete(void* p) {
    if (p == 0) return;
    cerr << "Freeing pointer @ " << p << "." << endl;
    free(p);
}

В заключение, несмотря на небрежную реализацию operator delete может потребоваться явная проверка нуля в клиентском коде. Это нестандартное поведение, и его следует допускать только при устаревшей поддержке (если вообще).

Удалить проверки для NULL внутри. Ваш тест избыточен

Удаление null - это запрет. Нет причин проверять наличие нуля перед вызовом удаления.

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

Согласно C ++ 03 5.3.5 / 2, можно удалить нулевой указатель. Это следующее цитата из стандарта:

  

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

Если pSomeObject имеет значение NULL, удаление ничего не даст. Так что нет, вам не нужно проверять NULL.

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

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

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

if(pSomeObject1!=NULL) pSomeObject1=NULL;

Но тогда delete в любом случае выполняет это точное сравнение (ничего не делая, если оно NULL). Зачем это делать дважды? Вы всегда можете назначить pSomeObject для NULL после вызова delete, независимо от его текущего значения - но это было бы немного избыточно, если бы оно уже имело это значение.

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

Это зависит от того, что вы делаете.Некоторые старые реализации free, например, не будут рады, если им передадут NULL указатель.В некоторых библиотеках до сих пор существует эта проблема.Например, XFree в библиотеке Xlib написано:

ОПИСАНИЕ

Функция XFREE-это обычная процедура XLIB, которая освобождает указанные данные.Вы должны использовать его, чтобы освободить любые объекты, которые были выделены XLIB, если для объекта явно указана альтернативная функция.Нулевый указатель не может быть передан этой функции.

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

Что касается моих наблюдений, удаление нулевого указателя с помощью delete безопасно на машинах на основе Unix ike PARISC и itanium. Но это совершенно небезопасно для систем Linux, так как тогда процесс завершится сбоем.

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