我经常看到旧代码在删除指针之前先检查 NULL
,类似于下面的代码:
if (NULL != pSomeObject)
{
delete pSomeObject;
pSomeObject = NULL;
}
在删除指针之前检查它是否为NULL
的原因是什么?把指针设置为NULL
之后的原因是什么?
我经常看到旧代码在删除指针之前先检查 NULL
,类似于下面的代码:
if (NULL != pSomeObject)
{
delete pSomeObject;
pSomeObject = NULL;
}
在删除指针之前检查它是否为NULL
的原因是什么?把指针设置为NULL
之后的原因是什么?
删除空指针是完全“安全”的,它实际上相当于无操作。
你可能想在删除之前检查是否为空指针,因为尝试删除空指针可能表示程序中存在错误。
编辑
注意:如果你重载了delete运算符,那么delete NULL
可能就不再“安全”。
delete
运算符的操作数是空指针,则不会发生任何操作。" - Randolphooperator delete
或operator delete[]
; §8.5.2.5/7, note)。
如果使用默认的释放函数(即标准库提供的),并且传入了空指针,则该调用不起作用(§6.6.4.4.2/3)。
但是如果释放函数不是由标准库提供的——即我们重载了operator delete
(或operator delete[]
),则未指定会发生什么。
一位称职的程序员应该在释放函数内部适当地处理空指针,而不是在调用之前,就像OP的代码所示。同样,在删除后将指针设置为nullptr
/NULL
只有非常有限的用途。一些人喜欢这样做是基于防御式编程的精神:在出现错误时,访问已删除的指针将导致对空指针的访问而不是访问随机的内存位置,这将使程序行为稍微更加可预测。虽然两种操作都是未定义的行为,但是空指针访问的行为在实践中更加可预测(它通常会直接崩溃而不是内存损坏)。由于内存损坏特别难以调试,重置已删除的指针有助于调试。
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
实现可能需要在客户端代码中进行显式的空值检查,但这是非标准行为,仅应在遗留支持(如有必要)中容忍。删除空值是无效操作。在调用delete之前检查null是没有必要的。
如果指针为null带有您关心的其他信息,则可能出于其他原因要检查null。
if (p != NULL) {dosomethingwith(p); delete p; }
这种常见模式来说是不冗余的。 - Jim Balter删除操作内部会检查 NULL。您的测试是多余的。
如果 pSomeObject
是 NULL ,delete
不会做任何事情。所以,不需要检查是否为 NULL。
我们认为,在删除后将指针赋值为 NULL 是一种良好的习惯,如果有可能某些笨蛋会尝试使用该指针。使用 NULL 指针要比使用指向未知内存的指针略好一些(NULL 指针会导致崩溃,指向已删除内存的指针可能不会)
NULL
可能会隐藏严重问题,如果周围的代码非常防御性地检查所有指针是否针对 NULL
进行解引用。 - Wolf我相信之前的开发人员编写代码时使用了“冗余”来节省一些毫秒时间: 在删除对象后将指针设置为NULL是一个好习惯,所以你可以在删除对象后使用以下代码:
if(pSomeObject1!=NULL) pSomeObject1=NULL;
但是delete操作本身就会进行这种比较(如果为NULL则不执行任何操作)。为什么要重复做一遍呢?无论pSomeObject的当前值如何,您都可以在调用delete之后将其赋值为NULL-但是如果它已经具有该值,则这可能略显冗余。
因此,我认为这些代码的作者试图确保pSomeObject1在被删除后始终为NULL,而不会产生潜在的不必要的测试和赋值成本。
这取决于你在做什么。例如,一些旧版本的free
实现如果传递了一个NULL
指针将会出现问题。一些库仍然存在这个问题。例如,在Xlib库中的XFree
函数如下所述:
描述
XFree
函数是一个通用的Xlib例程,用于释放指定的数据。除非为对象明确指定了替代函数,否则必须使用它来释放由Xlib分配的任何对象。不能向此函数传递空指针。
因此,将释放NULL
指针视为错误,这样你就会更加安全。