Does calling a method on a NULL pointer which does

2020-04-17 07:55发布

问题:

Will the program:

#include <stdio.h>

struct foo
{
   void blah()  {printf("blah\n");}
   int i;
};

void main(int, char**)
{
   ((foo*)NULL)->blah();
}

Ever crash, or do anything other than output blah, on any compiler you are aware of? Will any function crash, when called via a NULL pointer, if it doesn't access any members (including the vtable)?

There have been other questions on this topic, for instance Accessing class members on a NULL pointer and Is it legal/well-defined C++ to call a non-static method that doesn't access members through a null pointer?, and it is always pointed out that this results in undefined behavior. But is this undefined in the real world, or only in the standard's world? Does any extant compiler not behave as expected? Can you think of any plausible reason why any future compiler wouldn't behave as expected?

What if the function does modify members, but the NULL ptr is guarded against. For instance,

void foo::blah()
{
   foo* pThis = this ? this : new foo();
   pThis->i++;
}

Edit: For the record, the reason I wanted this was to make the interface to my linked list class as easy and concise as possible. I wanted to initialize the list to NULL have idiomatic usage look like:

pList = pList->Insert(elt);
pList = pList->Remove(elt);
...

Where all the operators return the new head element. Somehow I didn't realize that using a container class would make things even easier, with no downside.

回答1:

Can you think of any plausible reason why any future compiler wouldn't behave as expected?

A helpful compiler might add code to access the real object under the hood in debug builds in the hope of helping you catch this issue in your code early in the development cycle.

What if the function does modify members, but the NULL ptr is guarded against. For instance,

void foo::blah()
{
   foo* pThis = this ? this : new foo();
   pThis->i++;
}

Since it is undefined behavior to call that function with a null pointer, the compiler can assume that the test will always pass and optimize that function to:

void foo::blah()
{
   this->i++;
}

Note that this is correct, since if this is not null, it behaves as-if the original code was executed, and if this was null, it would be undefined behavior and the compiler does not need to provide any particular behavior at all.



回答2:

Undefined behavior means you can't rely on what will happen. However it's sometimes useful to know what's happening under the covers while you're debugging so that you're not surprised when the impossible happens.

Most compilers will code this as a simple function with a hidden this parameter, and if the this parameter is never referenced the code will work as expected.

Checking for this == NULL might not work, depending on how aggressively your compiler optimizes. Since a well formed program couldn't possibly have this==NULL, the compiler is free to pretend that it will never happen and optimize away the if statement entirely. I know though that Microsoft's C++ will not make this optimization because their GetSafeHWND function relies on it working as expected.



回答3:

Trying to guard for this == NULL wouldn't give you any real desirable effect. Mainly dereferencing NULL pointer, AFAIK, is undefined. It works differently for different compilers. Let's say that it does work in one scenario (like this) it doesn't work for this scenarios or this (virtual functions). The second and third scenarios are understandable, since the instance doesn't have a vtable entry to check for which of the virtual functions to call. But I'm not sure the same can be said for the first.

The other thing that you need to consider is that any invalid pointer can also give the same type of error you'd want to guard against, like this. Notice that it successfully printed 'Foo' and then went into a runtime error trying to access a. This is because the memory location being pointed to by Test* t is invalid. The same behavior is seen here, when Test* t is NULL.

So, in general, avoid such behaviors and design in your code. It's not predictable and it would cause undesirable effect if someone comes after you and changes your code thinking it should behave as it did previously.