dereferencing null

Chad J chadjoan at __spam.is.bad__gmail.com
Mon Mar 5 22:04:43 PST 2012


On 03/06/2012 12:07 AM, Jonathan M Davis wrote:
>
> If you dereference a null pointer, there is a serious bug in your program.
> Continuing is unwise. And if it actually goes so far as to be a segfault
> (since the hardware caught it rather than the program), it is beyond a doubt
> unsafe to continue. On rare occasion, it might make sense to try and recover
> from dereferencing a null pointer, but it's like catching an AssertError. It's
> rarely a good idea. Continuing would mean trying to recover from a logic error
> in your program. Your program obviously already assumed that the variable
> wasn't null, or it would have checked for null. So from the point of view of
> your program's logic, you are by definition in an undefined state, and
> continuing will have unexpected and potentially deadly behavior.
>
> - Jonathan M Davis

This could be said for a lot of things: array-out-of-bounds exceptions, 
file-not-found exceptions, conversion exception, etc.  If the programmer 
thought about it, they would have checked the array length, checked for 
file existence before opening it, been more careful about converting 
things, etc.

To me, the useful difference between fatal and non-fatal things is how 
well isolated the failure is.  Out of memory errors and writes into 
unexpected parts of memory are very bad things and can corrupt 
completely unrelated sections of code.  The other things I've mentioned, 
null-dereference included, cannot do this.

Null-dereferences and such can be isolated to sections of code.  A 
section of code might become compromised by the dereference, but the 
code outside of that section is still fine and can continue working.

Example:

// riskyShenanigans does some dubious things with nullable references.
// It was probably written late at night after one too many caffeine
//   pills and alcoholic beverages.  This guy is operating under much
//   worse conditions and easier objectives than the guy writing
//   someFunc().
//
// Thankfully, it can be isolated.
//
int riskyShenanigans()
{
	Foo f = new Foo();
	... blah blah blah ...
	f = null; // surprise!
	... etc etc ...

	// Once this happens, we can't count on 'f' or
	//   anything else in this function to be valid
	//   anymore.
	return f.bar;
}

// The author of someFunc() is trying to be a bit more careful.
// In fact, they'll even go so far as to make this thing nothrow.
// Maybe it's a server process and it's not allowed to die.
//
nothrow void someFunc()
{
	int cheesecake = 7;
	int donut = 0;

	// Here we will make sure that riskyShenanigans() is
	//   well isolated from everything else.
	try
	{
		// All statefulness inside this scope cannot be
		//   trusted when the null dereference happens.
		donut = riskyShenanigans();
	}
	catch( NullDereferenceException e )
	{
		// donut can be recovered if we are very
		//   explicit about it.
		// It MUST be restored to some known state
		//   before we consider using it again.
		donut = 0; // (so we restore it.)
	}

	// At this point, we HAVE accounted for null-dereferences.
	// donut is either a valid value, or it is zero.
	// We know what it will behave like.
	omnom(donut);

	// An even stronger case:
	// cheesecake had nothing to do with riskyShenanigans.
	// It is completely impossible for that null-dereference
	//   to have touched the cheesecake in this code.
	omnom(cheesecake);
}

And if riskyShenanigans were to modify global state... well, it's no 
longer so well isolated anymore.  This is just a disadvantage of global 
state, and it will be true with many other possible exceptions too.

Long story short: I don't see how an unexpected behavior in one part of 
a program will necessarily create unexpected behavior in all parts of 
the program, especially when good encapsulation is practiced.

Thoughts?


More information about the Digitalmars-d mailing list