是否有任何理由在删除之前检查一个空指针?

时间:2022-12-25 21:42:58

I often see legacy code checking for NULL before deleting a pointer, similar to,

在删除指针之前,我经常看到遗留代码检查NULL,类似于,

if (NULL != pSomeObject) 
{
    delete pSomeObject;
    pSomeObject = NULL;
}

Is there any reason to checking for a NULL pointer before deleting it? What is the reason for setting the pointer to NULL afterwards?

在删除空指针之前,是否有任何理由检查空指针?之后将指针设置为NULL的原因是什么?

10 个解决方案

#1


61  

It's perfectly "safe" to delete a null pointer; it effectively amounts to a no-op.

删除空指针是完全“安全”的;这实际上相当于一个禁忌。

The reason you might want to check for null before you delete is that trying to delete a null pointer could indicate a bug in your program.

在删除之前,您可能想要检查null的原因是,试图删除空指针可能表明程序中存在错误。

#2


34  

The C++ standard guarantees that it is legal to use a null pointer in a delete-expression (§8.5.2.5/2). However, it is unspecified whether this will call a deallocation function (operator delete or operator delete[]; §8.5.2.5/7, note).

c++标准保证是合法使用空指针在delete-expression(§8.5.2.5/2)。但是,它是否会调用deallocation函数(操作符删除或操作符删除[];§8.5.2.5/7注意)。

If a default deallocation function (i.e. provided by the standard library) is called with a null pointer, then the call has no effect (§6.6.4.4.2/3).

如果一个默认的回收函数(即标准库提供的)和一个空指针,然后调用没有影响(§6.6.4.4.2/3)。

But it is unspecified what happens if the deallocation function is not provided by the standard library — i.e. what happens when we overload operator delete (or operator delete[]).

但是,如果标准库没有提供deallocation函数,那么将会发生什么呢?当重载操作符delete(或操作符delete[])时会发生什么?

A competent programmer would handle null pointers accordingly inside the deallocation function, rather than before the call, as shown in OP’s code.Likewise, setting the pointer to nullptr/NULL after the deletion only serves very limited purpose. Some people like to do this in the spirit of defensive programming: it will make program behaviour slightly more predictable in the case of a bug: accessing the pointer after deletion will result in a null pointer access rather than a access to a random memory location. Although both operations are undefined behaviour, the behaviour of a null pointer access is a lot more predictable in practice (it most often results in a direct crash rather than memory corruption). Since memory corruptions are especially hard to debug, resetting deleted pointers aids debugging.

称职的程序员会在deallocation函数中相应地处理空指针,而不是在调用之前,如OP的代码所示。同样,在删除后将指针设置为nullptr/NULL也只能达到非常有限的目的。有些人喜欢按照防御编程的精神来做:这将使程序行为在bug的情况下更加可预测:删除后访问指针将导致对空指针的访问,而不是对随机内存位置的访问。虽然这两个操作都是未定义的行为,但在实践中,空指针访问的行为要可预测得多(它通常导致直接崩溃,而不是内存损坏)。由于内存崩溃特别难以调试,重新设置已删除指针有助于调试。

— Of course this is treating the symptom rather than the cause (i.e. the bug). You should treat resetting pointers as code smell. Clean, modern C++ code will make memory ownership clear and statically checked (by using smart pointers or equivalent mechanisms), and thus provably avoid this situation.

-当然,这是治疗症状,而不是病因(即臭虫)。您应该将重设指针视为代码味道。干净、现代的c++代码将使内存所有权变得清晰和静态地检查(通过使用智能指针或等效机制),从而可以证明避免这种情况。

Bonus: An explanation of overloaded operator delete:

operator delete is (despite its name) a function that may be overloaded like any other function. This function gets called internally for every call of operator delete with matching arguments. The same is true for operator new.

操作符delete是一个可以像其他函数一样重载的函数。这个函数在内部调用操作符delete和匹配的参数。对于操作符new也是如此。

Overloading operator new (and then also operator delete) makes sense in some situations when you want to control precisely how memory is allocated. Doing this isn't even very hard, but a few precautions must be made to ensure correct behaviour. Scott Meyers describes this in great detail Effective C++.

当你想精确控制内存分配时,重载操作符new(然后是操作符删除)在某些情况下是有意义的。这样做并不困难,但是必须采取一些预防措施来确保正确的行为。Scott Meyers详细介绍了高效c++。

For now, let's just say that we want to overload the global version of operator new for debugging. Before we do this, one short notice about what happens in the following code:

现在,让我们假设我们想要重载全局版本的运算符new以进行调试。在此之前,请注意以下代码中发生的事情:

klass* pobj = new klass;
// … use pobj.
delete pobj;

What actually happens here? Well the above can be roughly translated to the following code:

这里实际上发生了什么?以上可以大致翻译成以下代码:

// 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);

Notice step 2 where we call new with a slightly odd syntax. This is a call to so-called placement new which takes an address and constructs an object at that address. This operator can be overloaded as well. In this case, it just serves to call the constructor of the class klass.

注意第2步,我们使用稍微有点奇怪的语法调用new。这是对所谓的新位置的调用,它接受一个地址并在该地址构造一个对象。这个操作符也可以重载。在这种情况下,它只是用来调用类klass的构造函数。

Now, without further ado here's the code for an overloaded version of the operators:

现在,不再多说了,下面是重载版本的操作符代码:

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);
}

This code just uses a custom implementation of malloc/free internally, as do most implementations. It also creates a debugging output. Consider the following code:

这段代码只是在内部使用malloc/free的自定义实现,大多数实现也是如此。它还创建了一个调试输出。考虑下面的代码:

int main() {
    int* pi = new int(42);
    cout << *pi << endl;
    delete pi;
}

It yielded the following output:

它产生了以下输出:

Allocating 4 bytes of memory: @ 0x100160
42
Freeing pointer @ 0x100160.

Now, this code does something fundamentally different than the standard implementation of operator delete: It didn't test for null pointers! The compiler doesn't check this so the above code compiles but it may give nasty errors at run-time when you try to delete null pointers.

现在,这段代码做了一些与操作符delete的标准实现根本不同的事情:它没有测试空指针!编译器不会检查这个,所以上面的代码会编译,但是当你试图删除空指针时,它可能会在运行时产生严重的错误。

However, as I said before, this behaviour is actually unexpected and a library writer should take care to check for null pointers in the operator delete. This version is much improved:

但是,正如我前面所说的,这种行为实际上是出乎意料的,库作者应该注意检查操作符delete中的空指针。这个版本有了很大的改进:

void operator delete(void* p) {
    if (p == 0) return;
    cerr << "Freeing pointer @ " << p << "." << endl;
    free(p);
}

In conclusion, although a sloppy implementation of operator delete may require explicit null checks in the client code, this is non-standard behaviour and should only be tolerated in legacy support (if at all).

综上所述,尽管操作符delete的草率实现可能需要在客户端代码中进行显式的空检查,但这是不标准的行为,应该只允许遗留支持(如果有的话)。

#3


7  

Delete checks for NULL internally. Your test is redundent

在内部删除NULL检查。你的测试是redundent

#4


6  

Deleting null is a no-op. There's no reason to check for null before calling delete.

删除null是不操作的。在调用delete之前没有理由检查null。

You might want to check for null for other reasons if the pointer being null carries some additional information you care about.

如果指针为空,您可能需要检查null,因为它包含了您所关心的其他信息。

#5


4  

According to C++03 5.3.5/2, it's safe to delete a null pointer. This following is quoted from the standard:

根据c++ 03 5.3.5/2,删除空指针是安全的。以下引用自《标准》:

In either alternative, if the value of the operand of delete is the null pointer the operation has no effect.

在这两种方法中,如果delete操作数的值是空指针,那么操作就没有效果。

#6


3  

If pSomeObject is NULL, delete won't do anything. So no, you don't have to check for NULL.

如果pSomeObject是NULL,那么delete就不会做任何事情。不,你不需要检查NULL。

We consider it good practice to assign NULL to the pointer after deleting it if it's at all possible that some knucklehead can attempt to use the pointer. Using a NULL pointer is slightly better than using a pointer to who knows what (the NULL pointer will cause a crash, the pointer to deleted memory may not)

我们认为,在删除指针之后,如果某些傻瓜可以尝试使用指针,那么在删除指针后给它分配NULL是一种很好的做法。使用空指针比使用指向谁知道什么(空指针会导致崩溃,指向删除内存的指针)的指针稍微好一些。

#7


0  

There is no reason to check for NULL prior to delete. Assigning NULL after delete might be necessary if somewhere in the code checks are made whether some object is already allocated by performing a NULL check. An example would be some sort of cached data that is allocated on demand. Whenever you clear out the cache-object you assign NULL to the pointer so the code that allocates the object knows that it needs to perform an allocation.

没有理由在删除之前检查NULL。如果在代码检查中执行NULL检查,是否已经通过执行NULL检查分配了某个对象,那么可能需要在delete之后分配NULL。一个例子是根据需要分配的某种缓存数据。每当清除cache-object时,您将NULL分配给指针,以便分配对象的代码知道它需要执行分配。

#8


0  

It depends on what you are doing. Some older implementations of free, for example, will not be happy if they are passed a NULL pointer. Some libraries still have this problem. For example, XFree in the Xlib library says:

这取决于你在做什么。例如,一些较早的免费实现,如果它们被传递一个空指针,就会不高兴。一些库仍然存在这个问题。例如,Xlib库中的XFree说:

DESCRIPTION

描述

The XFree function is a general-purpose Xlib routine that frees the specified data. You must use it to free any objects that were allocated by Xlib, unless an alternate function is explicitly specified for the object. A NULL pointer cannot be passed to this function.

XFree函数是一个通用的Xlib例程,它释放指定的数据。您必须使用它释放Xlib分配的任何对象,除非为该对象显式指定了一个备用函数。不能将空指针传递给此函数。

So consider freeing NULL pointers as a bug and you'll be safe.

所以考虑释放空指针作为一个错误,您将是安全的。

#9


0  

I believe the previous developer coded it "redundantly" to save some milliseconds: It's a good thing to have the pointer be set to NULL upon being deleted, so you could use a line like the following right after deleting the object:

我相信之前的开发人员将它“冗余”编码以节省一些毫秒:删除对象时将指针设置为NULL是一件好事,因此您可以在删除对象之后使用如下代码:

if(pSomeObject1!=NULL) pSomeObject1=NULL;

But then delete is doing that exact comparison anyway (doing nothing if it's NULL). Why do this twice? You can always assign pSomeObject to NULL after calling delete, regardless of its current value - but this would be slightly redundant if it had that value already.

但是delete无论如何都做了精确的比较(如果它是空的,什么都不做)。为什么这两次吗?在调用delete之后,您总是可以将pSomeObject赋为NULL,而不考虑它的当前值——但是如果它已经有这个值,那么这个值就有点多余了。

So my bet is the author of those lines tried to ensure pSomeObject1 would always be NULL after being deleted, without incurring the cost of a potentially unnecessary test and assignation.

因此,我敢打赌,这些行的作者试图确保pSomeObject1在被删除后始终为NULL,而不会导致潜在不必要的测试和分配的成本。

#10


-5  

As for my observations, deleting a null pointer using delete is safe in unix based machines ike PARISC and itanium. But is quite unsafe for Linux systems as the process would crash then.

至于我的观察,在基于unix的机器ike PARISC和itanium中,使用delete删除空指针是安全的。但是对于Linux系统来说是非常不安全的,因为那时进程会崩溃。

#1


61  

It's perfectly "safe" to delete a null pointer; it effectively amounts to a no-op.

删除空指针是完全“安全”的;这实际上相当于一个禁忌。

The reason you might want to check for null before you delete is that trying to delete a null pointer could indicate a bug in your program.

在删除之前,您可能想要检查null的原因是,试图删除空指针可能表明程序中存在错误。

#2


34  

The C++ standard guarantees that it is legal to use a null pointer in a delete-expression (§8.5.2.5/2). However, it is unspecified whether this will call a deallocation function (operator delete or operator delete[]; §8.5.2.5/7, note).

c++标准保证是合法使用空指针在delete-expression(§8.5.2.5/2)。但是,它是否会调用deallocation函数(操作符删除或操作符删除[];§8.5.2.5/7注意)。

If a default deallocation function (i.e. provided by the standard library) is called with a null pointer, then the call has no effect (§6.6.4.4.2/3).

如果一个默认的回收函数(即标准库提供的)和一个空指针,然后调用没有影响(§6.6.4.4.2/3)。

But it is unspecified what happens if the deallocation function is not provided by the standard library — i.e. what happens when we overload operator delete (or operator delete[]).

但是,如果标准库没有提供deallocation函数,那么将会发生什么呢?当重载操作符delete(或操作符delete[])时会发生什么?

A competent programmer would handle null pointers accordingly inside the deallocation function, rather than before the call, as shown in OP’s code.Likewise, setting the pointer to nullptr/NULL after the deletion only serves very limited purpose. Some people like to do this in the spirit of defensive programming: it will make program behaviour slightly more predictable in the case of a bug: accessing the pointer after deletion will result in a null pointer access rather than a access to a random memory location. Although both operations are undefined behaviour, the behaviour of a null pointer access is a lot more predictable in practice (it most often results in a direct crash rather than memory corruption). Since memory corruptions are especially hard to debug, resetting deleted pointers aids debugging.

称职的程序员会在deallocation函数中相应地处理空指针,而不是在调用之前,如OP的代码所示。同样,在删除后将指针设置为nullptr/NULL也只能达到非常有限的目的。有些人喜欢按照防御编程的精神来做:这将使程序行为在bug的情况下更加可预测:删除后访问指针将导致对空指针的访问,而不是对随机内存位置的访问。虽然这两个操作都是未定义的行为,但在实践中,空指针访问的行为要可预测得多(它通常导致直接崩溃,而不是内存损坏)。由于内存崩溃特别难以调试,重新设置已删除指针有助于调试。

— Of course this is treating the symptom rather than the cause (i.e. the bug). You should treat resetting pointers as code smell. Clean, modern C++ code will make memory ownership clear and statically checked (by using smart pointers or equivalent mechanisms), and thus provably avoid this situation.

-当然,这是治疗症状,而不是病因(即臭虫)。您应该将重设指针视为代码味道。干净、现代的c++代码将使内存所有权变得清晰和静态地检查(通过使用智能指针或等效机制),从而可以证明避免这种情况。

Bonus: An explanation of overloaded operator delete:

operator delete is (despite its name) a function that may be overloaded like any other function. This function gets called internally for every call of operator delete with matching arguments. The same is true for operator new.

操作符delete是一个可以像其他函数一样重载的函数。这个函数在内部调用操作符delete和匹配的参数。对于操作符new也是如此。

Overloading operator new (and then also operator delete) makes sense in some situations when you want to control precisely how memory is allocated. Doing this isn't even very hard, but a few precautions must be made to ensure correct behaviour. Scott Meyers describes this in great detail Effective C++.

当你想精确控制内存分配时,重载操作符new(然后是操作符删除)在某些情况下是有意义的。这样做并不困难,但是必须采取一些预防措施来确保正确的行为。Scott Meyers详细介绍了高效c++。

For now, let's just say that we want to overload the global version of operator new for debugging. Before we do this, one short notice about what happens in the following code:

现在,让我们假设我们想要重载全局版本的运算符new以进行调试。在此之前,请注意以下代码中发生的事情:

klass* pobj = new klass;
// … use pobj.
delete pobj;

What actually happens here? Well the above can be roughly translated to the following code:

这里实际上发生了什么?以上可以大致翻译成以下代码:

// 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);

Notice step 2 where we call new with a slightly odd syntax. This is a call to so-called placement new which takes an address and constructs an object at that address. This operator can be overloaded as well. In this case, it just serves to call the constructor of the class klass.

注意第2步,我们使用稍微有点奇怪的语法调用new。这是对所谓的新位置的调用,它接受一个地址并在该地址构造一个对象。这个操作符也可以重载。在这种情况下,它只是用来调用类klass的构造函数。

Now, without further ado here's the code for an overloaded version of the operators:

现在,不再多说了,下面是重载版本的操作符代码:

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);
}

This code just uses a custom implementation of malloc/free internally, as do most implementations. It also creates a debugging output. Consider the following code:

这段代码只是在内部使用malloc/free的自定义实现,大多数实现也是如此。它还创建了一个调试输出。考虑下面的代码:

int main() {
    int* pi = new int(42);
    cout << *pi << endl;
    delete pi;
}

It yielded the following output:

它产生了以下输出:

Allocating 4 bytes of memory: @ 0x100160
42
Freeing pointer @ 0x100160.

Now, this code does something fundamentally different than the standard implementation of operator delete: It didn't test for null pointers! The compiler doesn't check this so the above code compiles but it may give nasty errors at run-time when you try to delete null pointers.

现在,这段代码做了一些与操作符delete的标准实现根本不同的事情:它没有测试空指针!编译器不会检查这个,所以上面的代码会编译,但是当你试图删除空指针时,它可能会在运行时产生严重的错误。

However, as I said before, this behaviour is actually unexpected and a library writer should take care to check for null pointers in the operator delete. This version is much improved:

但是,正如我前面所说的,这种行为实际上是出乎意料的,库作者应该注意检查操作符delete中的空指针。这个版本有了很大的改进:

void operator delete(void* p) {
    if (p == 0) return;
    cerr << "Freeing pointer @ " << p << "." << endl;
    free(p);
}

In conclusion, although a sloppy implementation of operator delete may require explicit null checks in the client code, this is non-standard behaviour and should only be tolerated in legacy support (if at all).

综上所述,尽管操作符delete的草率实现可能需要在客户端代码中进行显式的空检查,但这是不标准的行为,应该只允许遗留支持(如果有的话)。

#3


7  

Delete checks for NULL internally. Your test is redundent

在内部删除NULL检查。你的测试是redundent

#4


6  

Deleting null is a no-op. There's no reason to check for null before calling delete.

删除null是不操作的。在调用delete之前没有理由检查null。

You might want to check for null for other reasons if the pointer being null carries some additional information you care about.

如果指针为空,您可能需要检查null,因为它包含了您所关心的其他信息。

#5


4  

According to C++03 5.3.5/2, it's safe to delete a null pointer. This following is quoted from the standard:

根据c++ 03 5.3.5/2,删除空指针是安全的。以下引用自《标准》:

In either alternative, if the value of the operand of delete is the null pointer the operation has no effect.

在这两种方法中,如果delete操作数的值是空指针,那么操作就没有效果。

#6


3  

If pSomeObject is NULL, delete won't do anything. So no, you don't have to check for NULL.

如果pSomeObject是NULL,那么delete就不会做任何事情。不,你不需要检查NULL。

We consider it good practice to assign NULL to the pointer after deleting it if it's at all possible that some knucklehead can attempt to use the pointer. Using a NULL pointer is slightly better than using a pointer to who knows what (the NULL pointer will cause a crash, the pointer to deleted memory may not)

我们认为,在删除指针之后,如果某些傻瓜可以尝试使用指针,那么在删除指针后给它分配NULL是一种很好的做法。使用空指针比使用指向谁知道什么(空指针会导致崩溃,指向删除内存的指针)的指针稍微好一些。

#7


0  

There is no reason to check for NULL prior to delete. Assigning NULL after delete might be necessary if somewhere in the code checks are made whether some object is already allocated by performing a NULL check. An example would be some sort of cached data that is allocated on demand. Whenever you clear out the cache-object you assign NULL to the pointer so the code that allocates the object knows that it needs to perform an allocation.

没有理由在删除之前检查NULL。如果在代码检查中执行NULL检查,是否已经通过执行NULL检查分配了某个对象,那么可能需要在delete之后分配NULL。一个例子是根据需要分配的某种缓存数据。每当清除cache-object时,您将NULL分配给指针,以便分配对象的代码知道它需要执行分配。

#8


0  

It depends on what you are doing. Some older implementations of free, for example, will not be happy if they are passed a NULL pointer. Some libraries still have this problem. For example, XFree in the Xlib library says:

这取决于你在做什么。例如,一些较早的免费实现,如果它们被传递一个空指针,就会不高兴。一些库仍然存在这个问题。例如,Xlib库中的XFree说:

DESCRIPTION

描述

The XFree function is a general-purpose Xlib routine that frees the specified data. You must use it to free any objects that were allocated by Xlib, unless an alternate function is explicitly specified for the object. A NULL pointer cannot be passed to this function.

XFree函数是一个通用的Xlib例程,它释放指定的数据。您必须使用它释放Xlib分配的任何对象,除非为该对象显式指定了一个备用函数。不能将空指针传递给此函数。

So consider freeing NULL pointers as a bug and you'll be safe.

所以考虑释放空指针作为一个错误,您将是安全的。

#9


0  

I believe the previous developer coded it "redundantly" to save some milliseconds: It's a good thing to have the pointer be set to NULL upon being deleted, so you could use a line like the following right after deleting the object:

我相信之前的开发人员将它“冗余”编码以节省一些毫秒:删除对象时将指针设置为NULL是一件好事,因此您可以在删除对象之后使用如下代码:

if(pSomeObject1!=NULL) pSomeObject1=NULL;

But then delete is doing that exact comparison anyway (doing nothing if it's NULL). Why do this twice? You can always assign pSomeObject to NULL after calling delete, regardless of its current value - but this would be slightly redundant if it had that value already.

但是delete无论如何都做了精确的比较(如果它是空的,什么都不做)。为什么这两次吗?在调用delete之后,您总是可以将pSomeObject赋为NULL,而不考虑它的当前值——但是如果它已经有这个值,那么这个值就有点多余了。

So my bet is the author of those lines tried to ensure pSomeObject1 would always be NULL after being deleted, without incurring the cost of a potentially unnecessary test and assignation.

因此,我敢打赌,这些行的作者试图确保pSomeObject1在被删除后始终为NULL,而不会导致潜在不必要的测试和分配的成本。

#10


-5  

As for my observations, deleting a null pointer using delete is safe in unix based machines ike PARISC and itanium. But is quite unsafe for Linux systems as the process would crash then.

至于我的观察,在基于unix的机器ike PARISC和itanium中,使用delete删除空指针是安全的。但是对于Linux系统来说是非常不安全的,因为那时进程会崩溃。