我经常NULL
在删除指针之前看到遗留代码检查,类似于
if (NULL != pSomeObject) { delete pSomeObject; pSomeObject = NULL; }
NULL
在删除之前有没有理由检查指针?NULL
之后设置指针的原因是什么?
删除空指针是完全"安全的"; 它实际上相当于一个无操作.
您可能希望在删除之前检查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
有意义的)是有意义的.这样做甚至都不是很难,但必须采取一些预防措施以确保正确的行为.Scott Meyers详细描述了这个有效的C++.
现在,让我们说我们想要重载operator new
调试的全局版本.在我们这样做之前,请简要注意以下代码中发生的情况:
klass* pobj = new klass; // … use pobj. delete pobj;
这里到底发生了什么?以上可以粗略地翻译成以下代码:
// 1st step: allocate memory klass* pobj = static_cast(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
internal 的自定义实现.它还会创建一个调试输出.请考虑以下代码:
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的检查.你的测试是多余的
删除null是一个无操作.在调用delete之前没有理由检查null.
如果指针为null,则可能需要检查null是否存在其他原因.