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

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

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

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

10 ответов


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

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


стандарт C++ гарантирует, что законно использовать нулевой указатель в delete-expression (§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) имеет смысл в некоторых ситуациях, когда вы хотите контролировать то, как выделяется память. Это даже не очень сложно, но необходимо принять некоторые меры предосторожности, чтобы обеспечить правильное поведение. Скотт Мейерс описывает это очень подробно эффективное C++.

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


удаление null является no-op. Нет причин проверять значение null перед вызовом delete.

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


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

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


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

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


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


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

описание

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

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


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

if(pSomeObject1!=NULL) pSomeObject1=NULL;

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

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


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