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删除仅用于非常有限的目的。有些人喜欢本着defensive programming 的精神这样做:它会使程序行为在出现错误的情况下稍微更可预测:删除后访问指针将导致空指针访问,而不是对随机内存位置的访问.尽管这两个操作都是未定义的行为,但在实践中空指针访问的行为更容易预测(它通常会导致直接崩溃而不是内存损坏)。由于内存损坏特别难以调试,因此重置已删除的指针有助于调试。
——当然,这是治疗症状而不是治疗原因(即错误)。 您应该将重置指针视为代码异味。干净、现代的 C++ 代码将明确内存所有权并进行静态检查(通过使用智能指针或等效机制),因此可证明避免这种情况。
奖励:重载operator delete的解释:
operator delete 是(尽管它的名字)一个可以像任何其他函数一样被重载的函数。每次使用匹配的参数调用 operator delete 时,都会在内部调用此函数。 operator new 也是如此。
重载operator new(然后还有operator delete)在您想要精确控制内存分配方式的某些情况下是有意义的。这样做甚至不是很困难,但必须采取一些预防措施以确保正确的行为。 Scott Meyers 非常详细地描述了这一点Effective 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 的语法有点奇怪。这是对所谓的 placement 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 的草率实现可能需要在客户端代码中进行显式空检查,但这是非标准行为,只能在旧版支持中容忍(如果有的话) .