How to force a crash in C, is dereferencing a null pointer a (fairly) portable way?

Viewed 2957

I'm writing my own test-runner for my current project. One feature (that's probably quite common with test-runners) is that every testcase is executed in a child process, so the test-runner can properly detect and report a crashing testcase.

I want to also test the test-runner itself, therefore one testcase has to force a crash. I know "crashing" is not covered by the C standard and just might happen as a result of undefined behavior. So this question is more about the behavior of real-world implementations.

My first attempt was to just dereference a null-pointer:

int c = *((int *)0);

This worked in a debug build on GNU/Linux and Windows, but failed to crash in a release build because the unused variable c was optimized out, so I added

printf("%d", c); // to prevent optimizing away the crash

and thought I was settled. However, trying my code with clang instead of gcc revealed a surprise during compilation:

   [CC]   obj/x86_64-pc-linux-gnu/release/src/test/test/test_s.o
src/test/test/test.c:34:13: warning: indirection of non-volatile null pointer
      will be deleted, not trap [-Wnull-dereference]
    int c = *((int *)0);
            ^~~~~~~~~~~
src/test/test/test.c:34:13: note: consider using __builtin_trap() or qualifying
  pointer with 'volatile'
1 warning generated.

And indeed, the clang-compiled testcase didn't crash.

So, I followed the advice of the warning and now my testcase looks like this:

PT_TESTMETHOD(test_expected_crash)
{
    PT_Test_expectCrash();

    // crash intentionally
    int *volatile nptr = 0;
    int c = *nptr;
    printf("%d", c); // to prevent optimizing away the crash
}

This solved my immediate problem, the testcase "works" (aka crashes) with both gcc and clang.

I guess because dereferencing the null pointer is undefined behavior, clang is free to compile my first code into something that doesn't crash. The volatile qualifier removes the ability to be sure at compile time that this really will dereference null.

Now my questions are:

  • Does this final code guarantee the null dereference actually happens at runtime?
  • Is dereferencing null indeed a fairly portable way for crashing on most platforms?
5 Answers

No, dereferencing a NULL pointer is not a portable way of crashing a program. It is undefined behavior, which means just that, you have no guarantees what will happen.

As it happen, for the most part under any of the three main OS's used today on desktop computers, that being MacOS, Linux and Windows NT (*) dereferencing a NULL pointer will immediately crash your program.

That said: "The worst possible result of undefined behavior is for it to do what you were expecting."

I purposely put a star beside Windows NT, because under Windows 95/98/ME, I can craft a program that has the following source:

int main()
{
    int *pointer = NULL;
    int i = *pointer;
    return 0;
}

that will run without crashing. Compile it as a TINY mode .COM files under 16 bit DOS, and you'll be just fine.

Ditto running the same source with just about any C compiler under CP/M.

Ditto running that on some embedded systems. I've not tested it on an Arduino, but I would not want to bet either way on the outcome. I do know for certain that were a C compiler available for the 8051 systems I cut my teeth on, that program would run fine on those.

Related