So what’s in an exit status anyways?

May 13, 2010

Last time, we saw how we can capture a process’ core dump. The astute reader will have noticed that we seem to be pulling bits out of thin air:

int status;
if(wait(&status) < 0)
  perror("wait");
if(WIFSIGNALED(status) && WCOREDUMP(status))
...

We’ve got a 32-bit exit status, and yet we seem to getting two more useful bits of information out of it from the WIFSIGNALED() and WCOREDUMP() macros. How is that possible?

Well, what you thought was a 32-bit exit status really isn’t 32 bits. In fact, it’s quite a bit less than. The C standard only guarantees one useful bit. Quoth section 7.20.4.3, paragraph 5, of the C99 standard, which describes the exit(3) function:

Finally, control is returned to the host environment. If the value of status is zero or EXIT_SUCCESS, an implementation-defined form of the status successful termination is returned. If the value of status is EXIT_FAILURE, an implementation-defined form of the status unsuccessful termination is returned. Otherwise the status returned is implementation-defined.

Recall that implementation-defined means the C standard doesn’t define what happens, but the implementation (in this case, the GNU C library, or the Microsoft C library, etc.) must document the decision it made. Contrast this with undefined behavior, in which anything could happen (including erasing your hard drive), and nowhere does what happens have to be documented.

So if you want to write portable code, you only get one bit of information in your exit status: successful or unsuccessful termination, which is often good enough for most applications. If you go this route, it’s a good idea to use the EXIT_SUCCESS and EXIT_FAILURE macros, but it’s by no means necessary. You can use still use 0 and something non-0 (1 is a popular—and good—choice), and it will still work pretty much anywhere if you’re not unlucky. But the only truly 100% portable unsuccessful status is EXIT_FAILURE.

Screw that. You want more than one bit of information in your exit status. There’s a whole 32 bits (or occasionally 16 or 64 on some non-standard systems) in an int, so why can’t we use them? On Linux, the exit(3) man page clearly states we get 8 bits:

The exit() function causes normal process termination and the value of status & 0377 is returned to the parent (see wait(2)).

Mac OS X likewise also provides 8 bits (though that fact is a little more subtle in the documentation there). Windows fares better here—it provides the full 32 bits via the GetExitCodeProcess() function here—but the discussion here is going to focus on Linux/Mac OS X for now.

8 bits. Much more useful than 1, though not quite the 32 you might have been hoping for. It’s enough to express a varied gamut of exit statuses (incorrect usage, file not found, other unexpected error, etc.).

A consequence of this behavior is if you exit with a status that is a multiple of 256, that’s indistinguishable from 0, which means you’re likely exiting with a successful status when you meant it to be unsuccessful. Oops.

As a quick example, try out these shell commands ($? is a special parameter that evaluates to the exit status of the last child process or pipeline ran by the shell):

$ bash -c 'exit 5'; echo $?     # Prints 5
$ bash -c 'exit 256'; echo $?   # Prints 0 (!)

Now that we’ve figured out we only have 8 bits that come with an exit status, it’s clear how the WIFSIGNALED() and WCOREDUMP() macros work: wait(2) stuffs extra information into the status in addition to the child process’ exit status (you could have figured that out by reading the man page, but you obviously didn’t since you’re here reading this).

One final word of caution: be careful about exit statuses above 128. When a process is terminated due to a signal (say, because it segfaulted, resulting in a SIGSEGV), the exit status is 128 plus the signal number. Yes, a parent process can tell if the child process was terminated by a signal or by calling exit() by checking with WIFSIGNALED(), but it’s not always possible to get at that information when you want it. If you’re executing commands in the bash shell, you can get at the exit status quite easily with $?, but you can’t get at the other bits returned by wait(), at least not in any way I know. To keep things simple, if you never use exit statuses above 128, then anyone can unambiguously determine that an exit status of 0–127 means a normal exit, and an exit status of 128–255 means an abnormal exit.

In summary, use only EXIT_SUCCESS and EXIT_FAILURE for maximally portable code, and otherwise use only 0–127 for code that will be portable to Linux, Mac OS X, and Windows (and probably other not-uncommon systems that are still in current us but with which I’m not familiar enough to comment on).

Comments are closed.