14

I just saw in a code the following line :

#define ERR_FATAL( str, a, b, c ) {while(1) {*(unsigned int *)0 = 0xdeadbeef;} }

I know that 0xdeadbeef means error, but what putting this value mean when it's in address 0 ? What address 0 represents ?

user1047069
  • 935
  • 2
  • 13
  • 25
  • 1
    Better write it `0xDeadBeef` – Déjà vu Nov 09 '14 at 08:11
  • 1
    `0xdeadbeef` means nothing special. The point is to crash the program by dereferencing an invalid pointer - it could just as well write 0, or 0xffffffff, or 42. – user253751 Nov 09 '14 at 10:30
  • 1
    I've marked this a duplicate of an earlier question which asks about `*(long *)0 = 0` -- the difference between `long` and `unsigned int`, and between `0` and `0xdeadbeef` on the right-hand side of the assignment, makes only a trivial difference. Please note that the earlier answers include both an explanation of why this can't be *relied on* to crash the program, and why you might feel you have to do it anyway! (But even then there are better alternatives, e.g. [`__builtin_trap`](http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Other-Builtins.html#index-g_t_005f_005fbuiltin_005ftrap-3353).) – zwol Nov 09 '14 at 14:51

3 Answers3

11

The address 0x0 is recognized by the compiler as a NULL pointer and is going to be an implementation defined invalid memory address, on some systems is quite literally at the first addressable location in the system memory but on others it's just a placeholder in the code for some generic invalid memory address. From the point of view of the C code we don't know what address that will be, only that it's invalid to access it. Essentially what this code snippet is trying to do is to write to an "illegal" memory address, with the value 0xdeadbeef. The value itself is some hex that spells out "dead beef" hence indicating that the program is dead beef (ie. a problem), if you aren't a native english speaker I can see how this might not be so clear :). The idea is that this will trigger a segmentation fault or similar, with the intention of informing you that there is a problem by immediately terminating the program with no cleanup or other operations performed in the interim (the macro name ERR_FATAL hints at that). Most operating systems don't give programs direct access to all the memory in the system and the code presumes that the operating system won't let you directly access memory that's located at address 0x0. Given that you tagged this question with the linux tag this is the behavior you will see (because linux will not allow an access to that memory address). Note that if you are working on something like an embedded system where there's no such guarantee then this could cause a bunch of problems as you might be overwriting something important.

Note that there's going to be better ways out there to report problems than this that don't depend on certain types of undefined behaviors causing certain side effects. Using things like assert is going to likely be a better choice. If you want to terminate the program using abort() is a better choice as it in the standard library and does exactly what you want. See the answer from ComicSansMS for more about why this is preferable.

shuttle87
  • 15,466
  • 11
  • 77
  • 106
  • 1
    well he could simply call abort() – arash kordi Nov 09 '14 at 07:59
  • @arashkordi, there's certainly better ways of dealing with error states, this is something that I think is mostly just a historical practice and probably best left to the history books :) – shuttle87 Nov 09 '14 at 08:05
  • writing to 0 is technically undefined behavior, in windows you'll get a crash because that page is reserved and any pointers to there are illegal. – ratchet freak Nov 09 '14 at 14:19
  • 1
    It isn't possible to form a pointer to "the first addressable location in system memory" except by linker magic (e.g. a special external constant which is resolved to address 0) because *any integer constant expression with numeric value 0* converts to a null pointer when used in pointer context. Null pointers are *not* pointers to address zero, they point to *nothing*. In particular, the compiler is allowed to *optimize out* dereferences of a provably null pointer on the theory that code which has undefined behavior must be unreachable. – zwol Nov 09 '14 at 14:55
  • (Yes, this means that `char *x = '\0';` is a 100% strictly conforming initialization of `x` to a null pointer.) – zwol Nov 09 '14 at 14:56
7

Putting this value (or any value for that matter) in address 0 is supposed to terminate the program immediately.

A fatal error indicates an error situation so severe that the program cannot safely continue execution without risking further data corruption.

In particular, no pending cleanup operations are to be executed, as they could have an undesired effect. Think of flushing a buffer of corrupted data to the filesystem. Instead, the program is to terminate immediately and allow further examination of the situation via a core dump or an attached debugger.

Note that C already provides a standard library function for this purpose: abort(). Calling abort would be preferable for this purpose for a number of reasons:

  • Writing to address 0 is not guaranteed to terminate the program. This unnecessarily restricts portability of the code and might have devastating consequences in case the code actually gets recompiled and executed on a platform where writing to address 0 results in an actual memory store operation.
  • Calling abort() is more understandable to someone reading the code. Your question proves that many developers will not understand what the code in question is supposed to do. While the name of the macro and the value deadbeef give some hints, it is still unnecessarily obscure. Also, note that the name of the macro will not be visible when looking at the disassembled code in a debugger.
  • Calling abort() signals intent more clearly. This is not only true for the code itself, but also for the observable behavior of the binary. Assuming the operation executes as intended on a Unix machine, you would get a SIGSEGV as a result, which is a signal indicating memory corruption. abort() on the other hand causes SIGABRT which indicates an abnormal program termination. Unless the reason for the fatal error was indeed a memory corruption, throwing SIGSEGV in this case obscures why the program is failing and might be misleading to a developer trying to hunt down the error. This is particularly delicate when you think that the signal might not be caught by a debugger, but by an automated signal handler, which might then invoke unfitting code for error handling.

Therefore, if the sole intent of the macro is to signal a fatal error (as the name suggests), calling abort would be a better implementation.

ComicSansMS
  • 51,484
  • 14
  • 155
  • 166
  • 2
    It's not only the hardware, but also the compiler. Dereferencing a null pointer is UB. E.g. `clang` gives `warning: indirection of non-volatile null pointer will be deleted, not trap [-Wnull-dereference]` [...] `note: consider using __builtin_trap() or qualifying pointer with 'volatile'` for such code. – mafso Nov 09 '14 at 15:36
  • @mafso Good point. Compilers may decide to do the weirdest things for such code. In particular, code that worked fine for years might break horribly with a compiler upgrade. – ComicSansMS Nov 09 '14 at 15:40
1

Dereferencing an invalid pointer (which is what the above code is trying to do) results in Undefined Behavior. As such the system could do anything - it could crash, it could do nothing, or demons can fly out of your nose. This page is an excellent page on what every C programmer should know about Undefined Behavior.

Thus the above code is the wrong way to cause a crash. As a comenter pointed out, it would be better to call abort().

Craig S. Anderson
  • 6,966
  • 4
  • 33
  • 46
  • This is true but unhelpful. Note the [tag:linux] tag on this question, this isn't about an arbitrary C implementation. – Gilles 'SO- stop being evil' Nov 09 '14 at 08:05
  • Why is this unhelpful? I talk about standard C behavior. Most people don't know about undefined behavior. Your downvote is uncalled for. – Craig S. Anderson Nov 09 '14 at 08:06
  • 3
    I agree with @Gilles. This could be useful information if presented in addition to the implementation-specific behaviour the OP is hoping to understand, but in this case, by itself, it doesn't answer the question. –  Nov 09 '14 at 08:12
  • 1
    @Gilles note that Linux is not a C implementation. glibc and gcc are (or ulibc + clang or ...). And gcc has a [license to assume](http://blog.regehr.org/archives/918) that UB never happens so on Linux using standard tools the termination of program might not even happen on optimized build. – Maciej Piechotka Nov 09 '14 at 18:32