You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复exec()重定向stdout到管道后父进程读取失败的问题?

C代码错误分析与cksum管道通信修复方案

代码中的核心错误

  • 缓冲区未初始化且长度计算错误:get_cksum中buffer初始化为NULL,read尝试向空指针写入,直接触发非法内存访问,导致返回-1;同时sizeof(buffer)计算的是指针的大小(32位系统4字节、64位8字节),而非实际需要的缓冲区大小,无法容纳cksum的完整输出。
  • 子进程未关闭冗余管道描述符:子进程执行dup2(pipe[1], STDOUT_FILENO)后,未关闭原pipe[1]描述符,虽然后台会自动回收,但显式关闭是良好编程习惯,也避免管道写端引用计数未及时归零导致的潜在阻塞问题。
  • 错误的返回值类型:create_cksum_child的返回值类型是pid_t(整数类型),但fork失败时返回NULL(指针类型),类型不匹配,正确做法是返回-1表示错误。
  • 父进程未及时关闭管道写端:get_cksum创建管道后,父进程不需要写入管道,但未关闭fd[1],可能导致父进程读取管道时无法正确识别EOF信号(尽管子进程退出后会关闭写端,主动关闭冗余描述符更严谨)。
  • 父进程提前等待子进程:原代码中create_cksum_child里父进程直接调用waitpid,虽然不会直接导致读取失败,但会让父进程阻塞到子进程结束后再读取,属于逻辑冗余,且不利于异步场景扩展。

正确实现代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

// 创建子进程执行cksum,将输出重定向到管道
pid_t create_cksum_child(int pipe_fd[], const char *filename) {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork failed");
        return -1;
    }

    if (pid == 0) {
        // 子进程:关闭管道读端,重定向stdout到管道写端
        close(pipe_fd[0]);
        if (dup2(pipe_fd[1], STDOUT_FILENO) == -1) {
            perror("dup2 failed");
            exit(EXIT_FAILURE);
        }
        // 关闭冗余的管道写端
        close(pipe_fd[1]);

        // 执行cksum命令
        execlp("/usr/bin/cksum", "cksum", filename, NULL);
        // 如果execlp返回,说明执行失败
        perror("execlp failed");
        exit(EXIT_FAILURE);
    }

    // 父进程:关闭管道写端(不需要写入)
    close(pipe_fd[1]);
    return pid;
}

// 从管道读取cksum结果,返回动态分配的字符串(调用者需free)
char *get_cksum(const char *filename) {
    int pipe_fd[2];
    if (pipe(pipe_fd) < 0) {
        perror("pipe failed");
        return NULL;
    }

    pid_t child_pid = create_cksum_child(pipe_fd, filename);
    if (child_pid == -1) {
        close(pipe_fd[0]);
        close(pipe_fd[1]);
        return NULL;
    }

    // 初始化动态缓冲区
    char *buffer = NULL;
    size_t buffer_size = 256; // 初始大小足够容纳cksum输出
    ssize_t bytes_read;
    size_t total_read = 0;

    buffer = malloc(buffer_size);
    if (!buffer) {
        perror("malloc failed");
        close(pipe_fd[0]);
        waitpid(child_pid, NULL, 0); // 回收子进程,避免僵尸进程
        return NULL;
    }

    // 循环读取直到管道EOF
    while ((bytes_read = read(pipe_fd[0], buffer + total_read, buffer_size - total_read - 1)) > 0) {
        total_read += bytes_read;
        // 缓冲区不足时扩容
        if (total_read >= buffer_size - 1) {
            buffer_size *= 2;
            char *new_buffer = realloc(buffer, buffer_size);
            if (!new_buffer) {
                perror("realloc failed");
                free(buffer);
                close(pipe_fd[0]);
                waitpid(child_pid, NULL, 0);
                return NULL;
            }
            buffer = new_buffer;
        }
    }

    close(pipe_fd[0]);

    // 处理读取错误
    if (bytes_read < 0) {
        perror("read failed");
        free(buffer);
        waitpid(child_pid, NULL, 0);
        return NULL;
    }

    // 等待子进程结束,回收资源
    int status;
    waitpid(child_pid, &status, 0);
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
        fprintf(stderr, "child process exited with error\n");
        free(buffer);
        return NULL;
    }

    // 添加字符串结束符,可选去除末尾换行
    buffer[total_read] = '\0';
    if (total_read > 0 && buffer[total_read - 1] == '\n') {
        buffer[total_read - 1] = '\0';
    }

    return buffer;
}

// 测试示例
int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "usage: %s <filename>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    char *cksum_result = get_cksum(argv[1]);
    if (cksum_result) {
        printf("cksum result: %s\n", cksum_result);
        free(cksum_result);
    } else {
        fprintf(stderr, "failed to get cksum\n");
        exit(EXIT_FAILURE);
    }

    return EXIT_SUCCESS;
}

关键实现说明

  1. 管道重定向逻辑:

    • 子进程先关闭管道读端,仅保留写端用于输出;
    • 通过dup2将标准输出绑定到管道写端后,立即关闭原管道写端,避免冗余文件描述符;
    • 父进程创建子进程后马上关闭管道写端,确保子进程退出后管道写端完全关闭,父进程读取时能正确触发EOF。
  2. 缓冲区处理:

    • 使用动态扩容的缓冲区,确保能完整读取cksum的输出内容;
    • 循环读取管道直到EOF,避免单次读取长度不足导致的内容截断。
  3. 资源与错误管理:

    • 每个系统调用都添加错误检查,通过perror输出清晰的错误信息;
    • 确保所有文件描述符被正确关闭,避免资源泄漏;
    • 父进程通过waitpid回收子进程资源,避免僵尸进程;
    • 检查子进程退出状态,确保cksum命令执行成功。

内容的提问来源于stack exchange,提问作者Samuel H Page

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 18:03:18