In a C program on Linux, `waitpid()` stores an integer status for the child that ended. Why is that integer not simply the child's exit code, and how are you supposed to interpret it?
answer
- one integer, several possible endings
- classify before you extract
- the macros exist for a reason
- only eight bits ever survive
- siginfo_t says it without packing
basics
~20 sThe status is a packed word describing how the child ended, not just what it returned. Decode it with the macros: WIFEXITED plus WEXITSTATUS for a normal exit, WIFSIGNALED plus WTERMSIG when a signal killed it.
solid answer
~50 sA child can end in several genuinely different ways, and a single number cannot express them. So the wait family fills in a status *word* that first has to be classified. `WIFEXITED(status)` tells you it returned normally, and only then does `WEXITSTATUS(status)` give the value it passed to `exit()` — truncated to the low eight bits, so `exit(256)` reads back as 0. `WIFSIGNALED(status)` says a signal terminated it, `WTERMSIG(status)` names which, and `WCOREDUMP(status)` says whether a core file was produced. With `WUNTRACED` or `WCONTINUED` you can also see stopped and resumed children via `WIFSTOPPED`/`WSTOPSIG`. The layout itself is deliberately unspecified by POSIX; reading the raw integer is a portability bug. `waitid()` is the modern alternative — it fills a `siginfo_t` whose `si_code` is `CLD_EXITED`, `CLD_KILLED` or `CLD_DUMPED`, with the value in `si_status`, so nothing needs unpacking.
code
c · 21 lines#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void) {
pid_t pid = fork();
if (pid == 0) {
exit(42);
}
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("exited with %d\n", WEXITSTATUS(status)); /* 42 */
} else if (WIFSIGNALED(status)) {
printf("killed by signal %d\n", WTERMSIG(status));
}
return 0;
}go deeper
Know that the value waitpid() gives back is not the exit code itself, and that you read it with WIFEXITED and WEXITSTATUS rather than using the number as-is.
Explain why a single integer must encode several outcomes, walk through the macro set including WIFSIGNALED and WTERMSIG, and mention the eight-bit truncation of exit codes.
Show why a supervisor must distinguish a clean non-zero exit from a signal death — the retry, alert and escalation decisions differ — and why hand-decoding the raw word is a defect that misreports exactly the incidents you care about.
Own the convention across the platform: define what exit codes a service is allowed to mean, treat them as the one-byte channel they are, and require richer failure detail to travel over logs or structured output rather than being crammed into a status.
## The problem the status word solves "How did my child end?" is not a yes/no question and not a single number. The plausible outcomes are at least these: - it ran to completion and returned a value of its own choosing; - a signal terminated it, and possibly dumped core; - it was stopped (job control or a debugger) and is still alive; - it was continued after being stopped. An exit code alone can express only the first. Worse, the space of exit codes (0–255) is fully controlled by the child, so there is no value the kernel could reserve to mean "actually, I was killed". The wait family therefore returns a small classified record, packed into an `int` for historical reasons, and gives you macros to read it. ## The decoding contract The rule is: **classify first, extract second.** Extracting before classifying is meaningless. ```c int status; pid_t pid = waitpid(child, &status, 0); if (WIFEXITED(status)) { printf("exited with %d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("killed by signal %d%s\n", WTERMSIG(status), WCOREDUMP(status) ? " (core dumped)" : ""); } else if (WIFSTOPPED(status)) { printf("stopped by signal %d\n", WSTOPSIG(status)); } ``` The macros in full: - `WIFEXITED` — true for normal termination. Then `WEXITSTATUS` yields the value passed to `exit()`/`_exit()`, or returned from `main()`. - `WIFSIGNALED` — true when a signal terminated it. Then `WTERMSIG` gives the signal number and `WCOREDUMP` (widely available, not in every standard) says whether a core dump was written. - `WIFSTOPPED` — true for a child that is stopped rather than dead; only reported if you passed `WUNTRACED` (or the child is being traced). `WSTOPSIG` gives the stopping signal. - `WIFCONTINUED` — true for a child resumed by `SIGCONT`, reported when you pass `WCONTINUED`. ## The eight-bit truncation `WEXITSTATUS` yields only the low 8 bits of what the child passed to `exit()`. `exit(256)` is observed as 0 — a genuinely nasty bug when a program returns something like a count or a negative errno: `exit(-1)` is observed as 255. Exit codes are a one-byte channel; anything richer must travel over a pipe, a file or a log line. ## Why you must not read the integer directly POSIX deliberately declines to specify the layout. On Linux and most Unix systems the terminating signal sits in the low seven bits, a core-dump flag beside it, and the exit code in bits 8–15 — which is why the naive `status >> 8` appears in old code. Writing that is a portability defect, and it silently produces nonsense on a status that represents a signal death rather than an exit. Use the macros; they exist precisely so the layout can differ. A related point of confusion: the number a *shell* reports for a signalled child is a convention the shell synthesises for its own users. It is not what sits in this status word, and code that expects one to equal the other is wrong. ## The modern alternative: waitid() `waitid()` sidesteps packing altogether. You pass a `siginfo_t`, and the kernel fills in structured fields: ```c siginfo_t info; waitid(P_PID, child, &info, WEXITED); /* info.si_code is CLD_EXITED, CLD_KILLED or CLD_DUMPED; info.si_status holds the exit code or the signal number. */ ``` Because the classification lives in `si_code` rather than in bit positions, there is nothing to decode and no truncation surprise in the API shape itself. `waitid()` also supports `WNOWAIT`, which reports a child's status *without* reaping it, so another part of the program can still collect it later. ## Where this shows up in practice Any supervisor, test runner, build tool or process pool that launches children has to answer "did it succeed, or was it killed?" and must answer it correctly to decide whether to retry, alert, or treat the result as a legitimate failure of the work itself. Code that collapses the two — treating a signal death as if the program had returned that number — reports garbage in exactly the incidents where accuracy matters most.
- What does WEXITSTATUS return if the child called exit(256)?Zero. Only the low eight bits of the value survive, so 256 wraps to 0 and `exit(-1)` reads back as 255. Exit codes are a one-byte channel by design; anything wider — a count, an errno, a message — has to travel over a pipe, a file or a log line instead.
- Why is reading `status >> 8` instead of using WEXITSTATUS a real bug and not just a style issue?POSIX leaves the packing unspecified, so the shift is not portable. Worse, it is unconditional: applied to a status that actually represents a signal death it yields a meaningless number that the program then treats as an exit code, turning "the child was killed" into a plausible-looking success or failure value.
- When would you reach for waitid() rather than waitpid()?When you want the classification without bit-twiddling — `si_code` is `CLD_EXITED`, `CLD_KILLED` or `CLD_DUMPED` and `si_status` carries the value — or when you need `WNOWAIT`, which reports a child's status while deliberately leaving it unreaped so another part of the program can still collect it.
It is a discharge form, not a score: first you read the box that says whether the patient walked out or was carried out, and only then does the number underneath mean anything.
saying these in an interview costs you the question
- Treats the status integer as the exit code directly
- Uses status >> 8 without checking WIFEXITED first
- Thinks an exit code can carry values above 255
- Believes a signalled child reports a normal exit status
- Cannot distinguish WTERMSIG from WEXITSTATUS