Status Macros
Intermediate
3 Programs
What is Wait Status?
When wait() or waitpid() fills in the status integer, it encodes information about how the child changed state. You should never inspect the raw bits directly — always use the standard W* macros from <sys/wait.h>.
Four events are possible:
- Child called
_exit()orexit()— normal termination - Child was killed by an unhandled signal
- Child was stopped by a signal (with WUNTRACED)
- Child was resumed by SIGCONT (with WCONTINUED)
| Bit 15-8 | Bit 7 | Bit 6-0 | Meaning |
| exit status (0-255) | 0 | 0x00 | Normal exit via _exit() |
| 0 | core flag | signal no. ≠ 0 | Killed by signal |
| stop signal | 0 | 0x7F | Stopped by signal |
| 0xFFFF | Continued by SIGCONT | ||
Note: Always use macros — never inspect these bits directly. The layout is implementation-specific.
| Macro | Returns true when… | Companion macro |
|---|---|---|
| WIFEXITED(status) | Child called _exit() / exit() | WEXITSTATUS(status) — exit code (0-255) |
| WIFSIGNALED(status) | Child killed by unhandled signal | WTERMSIG(status) — signal number WCOREDUMP(status) — core dump produced? |
| WIFSTOPPED(status) | Child stopped by a signal | WSTOPSIG(status) — stop signal number |
| WIFCONTINUED(status) | Stopped child resumed via SIGCONT | (no companion; just the flag itself) |
Important: Only ONE of the WIFEXITED / WIFSIGNALED / WIFSTOPPED / WIFCONTINUED macros will be true for any given status value.
This utility function decodes any wait status and prints a human-readable description. Based on the standard pattern from TLPI.
#define _GNU_SOURCE /* for strsignal() */
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
void print_wait_status(const char *msg, int status)
{
if (msg != NULL)
printf("%s: ", msg);
if (WIFEXITED(status)) {
printf("child exited normally, exit status = %d\n",
WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("child killed by signal %d (%s)",
WTERMSIG(status),
strsignal(WTERMSIG(status)));
#ifdef WCOREDUMP
if (WCOREDUMP(status))
printf(" [core dumped]");
#endif
printf("\n");
} else if (WIFSTOPPED(status)) {
printf("child stopped by signal %d (%s)\n",
WSTOPSIG(status),
strsignal(WSTOPSIG(status)));
#ifdef WIFCONTINUED
} else if (WIFCONTINUED(status)) {
printf("child continued (SIGCONT received)\n");
#endif
} else {
/* Should never happen */
printf("unknown status: 0x%x\n", (unsigned int)status);
}
}
/* --- Test harness --- */
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char *argv[])
{
pid_t child;
int status;
child = fork();
if (child == 0) {
if (argc > 1) {
/* exit with given code */
exit(atoi(argv[1]));
} else {
/* wait for signals */
for (;;) pause();
}
}
/* Parent waits, handling all state changes */
for (;;) {
child = waitpid(-1, &status,
WUNTRACED
#ifdef WCONTINUED
| WCONTINUED
#endif
);
if (child == -1) {
perror("waitpid");
break;
}
print_wait_status("Status", status);
if (WIFEXITED(status) || WIFSIGNALED(status))
break;
}
return 0;
}
/*
* Test runs:
* ./a.out 0 -> child exited normally, exit status = 0
* ./a.out 42 -> child exited normally, exit status = 42
* ./a.out & -> kill -STOP <child> -> child stopped
* -> kill -CONT <child> -> child continued
* -> kill -TERM <child> -> child killed by signal 15
*/
Mimics how shells check exit status — success (0) vs failure (non-zero) vs signal death.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
/* Returns: 0 if child exited OK, -1 if error/signal, positive = exit code */
int run_child(int exit_code, int send_signal)
{
pid_t child;
int status;
child = fork();
if (child == 0) {
if (send_signal)
raise(send_signal); /* kill ourselves */
exit(exit_code);
}
waitpid(child, &status, 0);
if (WIFEXITED(status)) {
int code = WEXITSTATUS(status);
printf("Process exited with code %d — %s\n",
code, code == 0 ? "SUCCESS" : "FAILURE");
return code;
} else if (WIFSIGNALED(status)) {
printf("Process killed by signal %d — ABNORMAL TERMINATION\n",
WTERMSIG(status));
return -1;
}
return -1;
}
int main(void)
{
printf("--- Test 1: normal success ---\n");
run_child(0, 0);
printf("--- Test 2: normal failure ---\n");
run_child(1, 0);
printf("--- Test 3: killed by SIGTERM ---\n");
run_child(0, 15); /* SIGTERM = 15 */
printf("--- Test 4: exit code 127 (command not found) ---\n");
run_child(127, 0);
return 0;
}
Shows how to detect if a child produced a core dump file after being killed by a signal.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/resource.h>
int main(void)
{
pid_t child;
int status;
struct rlimit rl;
/* Enable core dumps (set limit to unlimited) */
rl.rlim_cur = RLIM_INFINITY;
rl.rlim_max = RLIM_INFINITY;
setrlimit(RLIMIT_CORE, &rl);
child = fork();
if (child == 0) {
/* Cause SIGABRT which by default dumps core */
abort(); /* sends SIGABRT to self */
}
waitpid(child, &status, 0);
if (WIFSIGNALED(status)) {
printf("Child killed by signal: %d (%s)\n",
WTERMSIG(status),
strsignal(WTERMSIG(status)));
#ifdef WCOREDUMP
if (WCOREDUMP(status))
printf("Core dump was produced — check 'core' file\n");
else
printf("No core dump (may be disabled by ulimit)\n");
#endif
}
return 0;
}
/*
* Compile and run:
* gcc core_demo.c -o core_demo
* ./core_demo
* Output:
* Child killed by signal: 6 (Aborted)
* Core dump was produced — check 'core' file
*/
Although status is declared as int (typically 4 bytes), only the bottom 16 bits (2 bytes) are actually used by the kernel to encode child state. The upper 2 bytes are always zero.
This is why you should never directly inspect or compare the raw status integer. Always use the macros — they are portable and correctly mask the relevant bits.
/* BAD — never do this */
if (status == 0) { ... } /* wrong way */
/* GOOD — always use macros */
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { ... }
Q1. Why should you use W* macros instead of directly inspecting the status integer?
The layout of status bits is implementation-specific. Macros provide a portable, standard interface that works across all POSIX-compliant systems.
Q2. What does WEXITSTATUS(status) return if the child was killed by a signal?
It’s undefined. You must first verify WIFEXITED(status) == true before calling WEXITSTATUS().
Q3. Can more than one W*IF* macro be true at the same time for the same status?
No. Only one of WIFEXITED, WIFSIGNALED, WIFSTOPPED, or WIFCONTINUED will be true for any given status value.
Q4. How does the shell set $? based on wait status?
If WIFEXITED: $? = WEXITSTATUS(status). If WIFSIGNALED: $? = 128 + signal_number.
Q5. What is WCOREDUMP and why is it not in SUSv3?
WCOREDUMP returns true if the child produced a core dump. It’s not in SUSv3 because core dump behavior is not standardized — it’s available on most Unix implementations including Linux.
Q6. A child calls exit(256). What does WEXITSTATUS() return?
0. Only the least significant 8 bits of the exit status are available to the parent. 256 & 0xFF == 0.
Q7. What is the difference between termination status and wait status?
Termination status refers only to normal exit or signal death. Wait status is broader — it includes stopped and continued states too.
Q8. Write code to detect if a child exited with an error (non-zero exit code).
waitpid(child, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
fprintf(stderr, "Child failed with exit code %d\n",
WEXITSTATUS(status));
