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