Есть ли причина проверять NULL-указатель перед удалением? - PullRequest
48 голосов
/ 05 марта 2009

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

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

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

Ответы [ 10 ]

67 голосов
/ 05 марта 2009

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

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

35 голосов
/ 05 марта 2009

Стандарт 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 может потребовать явных проверок на ноль в клиентском коде, это нестандартное поведение и должно допускаться только в устаревшей поддержке (, если вообще ).

9 голосов
/ 05 марта 2009

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

7 голосов
/ 05 марта 2009

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

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

4 голосов
/ 21 апреля 2012

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

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

3 голосов
/ 05 марта 2009

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

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

1 голос
/ 05 марта 2009

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

0 голосов
/ 07 августа 2009

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

0 голосов
/ 05 марта 2009

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

if(pSomeObject1!=NULL) pSomeObject1=NULL;

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

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

0 голосов
/ 05 марта 2009

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

ОПИСАНИЕ

Функция XFree является универсальная рутина Xlib, которая освобождает указанные данные. Вы должны использовать его, чтобы освободить любые объекты, которые были выделяется Xlib, если не альтернатива функция явно указана для предмет. НЕДЕЙСТВИТЕЛЬНЫЙ указатель не может быть перешел к этой функции.

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

...