C 语言解析子进程退出状态:WIFEXITED 与 WEXITSTATUS 正确用法
POSIX 程序里用 fork + exec 启动子进程后,必须靠 wait 系列函数回收子进程状态。很多人直接拿 status == 0 判断成功,结果在子进程被信号杀死时误判。正确做法是使用 WIFEXITED / WEXITSTATUS / WIFSIGNALED / WTERMSIG 这组宏解析状态码。本文说明状态码的编码方式与标准写法。

一、为什么要用宏而不是直接比较
wait(&status) 里的 status 是内核编码的综合状态,包含“退出方式”和“退出码/信号号”两部分,格式在不同实现上有差异(传统上高 8 位是退出码,低 7 位是信号)。因此标准做法永远是通过宏提取:
#include <sys/wait.h>
int status;
pid_t pid = wait(&status); // 或 waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("正常退出, 退出码=%d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("被信号杀死, 信号=%d\n", WTERMSIG(status));
}
/* 还有 WIFSTOPPED / WSTOPSIG(配合 WUNTRACED 用) */
二、四个宏各自回答什么问题
WIFEXITED(status):子进程是否正常调用 exit/_exit/return from main 结束。为真时才有意义地取 WEXITSTATUS。WEXITSTATUS(status):子进程的退出码(0–255)。0 通常表示成功。WIFSIGNALED(status):是否因未捕获的信号终止(如 SIGSEGV、SIGKILL、Ctrl+C 的 SIGINT)。WTERMSIG(status):导致终止的信号编号。
关键认知:被信号杀死的子进程,WEXITSTATUS 的值没有意义。直接比较 status == 0 会在这类情况下得出错误结论。
三、完整示例:包装 system 并区分三类失败
int run_cmd(const char *cmd, int *out_code, int *out_sig) {
pid_t pid = fork();
if (pid < 0) return -1; // fork 失败
if (pid == 0) {
execlp("/bin/sh", "sh", "-c", cmd, (char*)NULL);
_exit(127); // exec 失败:约定 127
}
int status;
while (waitpid(pid, &status, 0) < 0) {
if (errno == EINTR) continue; // 被信号打断要重试
return -1;
}
if (WIFEXITED(status)) { *out_code = WEXITSTATUS(status); *out_sig = 0; }
else if (WIFSIGNALED(status)) { *out_code = -1; *out_sig = WTERMSIG(status); }
return 0;
}
要点:waitpid 可能被信号打断返回 EINTR,必须循环重试;shell 找不到命令时的约定退出码是 127,超时时限被杀则是 124 一类,这些都是惯例而非标准。
四、与 shell 退出码的对应关系
子进程退出码 0–255(只有低 8 位保留,exit(256) 会变成 0)。常见约定:
0成功;1通用失败;126找到但不可执行;127命令未找到;128+n:shell 报告“因信号 n 终止”,如139 = 128+11(SIGSEGV)、137 = 128+9(SIGKILL,容器 OOM 常见值)。
这解释了为什么 bash 里看到 137 会怀疑内存超限:kill -9 与 OOM Killer 都会留下 137。
五、僵尸进程与 wait 的义务
子进程结束后会保留退出状态,直到父进程 wait 取走;不取就变僵尸(Z 状态)。规范处理方式:
/* 方案1:不关心结果时显式忽略 SIGCHLD 的默认行为 */
struct sigaction sa = {0};
sa.sa_handler = SIG_IGN;
sa.sa_flags = SA_RESTART;
sigaction(SIGCHLD, &sa, NULL);
/* 方案2:waitpid(-1, &status, WNOHANG) 轮询回收,不阻塞 */
while (waitpid(-1, &status, WNOHANG) > 0) { /* 解析 */ }
注意方案 1 会让程序无法获知退出码,只适合完全不关心的场景;服务程序更常用把 SIGCHLD 处理函数里做 WNOHANG 循环回收。
六、常见问题 FAQ
问:system() 返回值能直接当退出码吗?
答:不能,system 返回值同样是 wait 状态格式,必须用 WIFEXITED/WEXITSTATUS 解析;且 system 会受 SIGINT/SIGQUIT 影响、有 shell 注入风险,应用 fork+exec。
问:wait 与 waitpid 差别?
答:wait 阻塞等任意子进程;waitpid 可指定 pid、可带 WNOHANG/WUNTRACED 非阻塞与跟踪停止,是多子进程场景的正确选择。
问:WIFEXITED 为假、WIFSIGNALED 也为假?
答:说明是停止(WIFSTOPPED,需 WUNTRACED 才能观察到)或实现扩展(core dumped 用 WCOREDUMP)。
结论:永远不要直接比较 status;先 WIFEXITED 再 WEXITSTATUS、WIFSIGNALED 再 WTERMSIG;waitpid 需处理 EINTR 重试;不回收就会留僵尸;system 有注入与信号风险,优先 fork+exec。
生产内容,同时打通搜索引擎(SEO)与AI搜索引擎(GEO)流量入口的软件! 点击进入365建站器 »
