您现在的位置: 365建站网 > 365文章 > C 语言解析子进程退出状态:WIFEXITED 与 WEXITSTATUS 正确用法

C 语言解析子进程退出状态:WIFEXITED 与 WEXITSTATUS 正确用法

文章来源:365jz.com 点击数:12 更新时间:2026-05-04 20:15

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

C语言正确解析子进程退出状态wait与WEXITSTATUS

一、为什么要用宏而不是直接比较

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建站器 »