Что будет, если вызвать delete (или delete[]) на nullptr в C++?

Ответ

Это абсолютно безопасная и корректная операция. Согласно стандарту C++ (начиная с C++11), вызов delete или delete[] на нулевом указателе (значении nullptr или унаследованном NULL) не имеет никакого эффекта (является no-op).

Почему это полезно: Это избавляет от необходимости явных проверок перед удалением.

MyClass* ptr = nullptr;
// ... какой-то код, который может или не может выделить память ...
delete ptr; // Всегда безопасно. Если ptr == nullptr, ничего не происходит.

Важные уточнения:

  1. До C++11 поведение было неопределённым (Undefined Behavior), хотя большинство реализаций также обрабатывали это безопасно. Для полной переносимости в старом коде иногда делали проверку if (ptr) delete ptr;.
  2. Двойное удаление (double delete) — это всегда UB, даже если первый вызов был на nullptr.
    int* p = new int(42);
    delete p; // Освобождает память
    p = nullptr; // Обнуляем указатель
    delete p; // Безопасно, ничего не делает (p == nullptr)
    // НО:
    delete p; // Второй вызов delete на том же нулевом указателе — всё ещё безопасный no-op.
  3. Это правило работает только для стандартного оператора delete. Для пользовательских функций освобождения памяти (placement delete, custom deallocators) поведение определяется их реализацией.

Ответ 18+ 🔞

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

Представь себе: у тебя есть указатель. Он либо показывает на кучу памяти, либо в никуда (nullptr). И ты такой: "Так, надо почистить за собой". Раньше, в каменном веке до C++11, народ писал проверки, типа:

if (myPtr != nullptr) {
    delete myPtr;
}

А сейчас? Ёпта, да похуй! Серьёзно. Стандарт говорит — вызывай delete на nullptr, и ничего не произойдёт. Вообще. Никакого эффекта. Это как крикнуть в пустую комнату — звук есть, а последствий ноль.

MyClass* ptr = nullptr;
// ... тут могла быть твоя жизнь, а могла и не быть ...
delete ptr; // Абсолютно безопасно. Если ptr нулевой, компилятор просто пожмёт плечами и пойдёт дальше.

Но есть нюансы, блядь, куда же без них:

  1. Исторический экскурс. До 2011 года формально это было неопределённым поведением. Хотя все компиляторы делали вид, что так и надо, и тоже ничего не делали. Так что если видишь в легаси-коде проверку перед delete — теперь понимаешь, откуда у неё ноги растут. Это пережиток прошлого, как кнопка «Сохранить» в виде дискеты.

  2. Главная ловушка — двойное удаление. Вот тут уже будет тебе хиросима и нигасраки. Это UB (Undefined Behavior) в чистом виде. Даже если ты после первого удаления занулил указатель.

    int* p = new int(42);
    delete p; // Освободил память. В этот момент p уже "висячий" указатель (dangling).
    p = nullptr; // Занулил. Молодец.
    delete p; // Удалил nullptr. Безопасно, ничего не делаем.
    // Всё ок? Нет, блядь!
    // А если бы не занулил? delete p; // Второй раз по тому же адресу? Пиздец. Скрепы пошли.

    Суть в том, что опасен не второй вызов delete на nullptr, а второй вызов delete на одну и ту же, уже освобождённую память. Зануление — это просто хороший тон, чтобы не наступить на эти грабли случайно.

  3. Это работает только для стандартного delete. Если ты какой-нибудь хитрый жопа и написал свой аллокатор или используешь placement new, то там свои правила. Твой кастомный механизм очистки может и не ожидать нулевого указателя. Так что тут уже без страховки.

Итог: С современным C++ можешь не париться. delete nullptr — это легально, безопасно и удобно. Главное — следи, чтобы не удалить одну память дважды, вот где собака зарыта.