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

fork环境下fgets读取文件未终止,重复循环读取行问题

问题分析与解决

核心问题原因

  1. 文件描述符泄漏引发未定义行为
    循环中每次重新打开临时文件Tmp前,未关闭之前打开的Tmp指针,导致文件描述符持续泄漏。当描述符数量达到系统上限后,fopen会失败,Tmp变为NULL。后续子进程调用write(fileno(Tmp), ...)时,fileno(NULL)属于未定义行为,可能意外写入到其他打开的文件描述符(比如输入文件Nombres),导致输入文件内容被篡改,父进程的fgets重复读取旧内容,循环无法终止。

  2. 父子进程stdio缓冲区共享导致输出重复
    最初用fprintf时,父子进程都持有Output文件的stdio用户态缓冲区,子进程退出时会自动刷新缓冲区,导致同一段内容被父进程和子进程各写入一次,出现重复行。改用write直接操作内核文件描述符则不会有这个问题,因为write不经过用户态缓冲区,内核会维护统一的文件偏移量。

解决步骤

  1. 关闭旧文件指针,避免泄漏
    每次重新打开临时文件前,关闭之前的Tmp指针,释放文件描述符。
  2. 子进程关闭无关文件
    子进程只需要操作临时文件,关闭其他无关的文件描述符,减少资源占用和意外操作的风险。
  3. 添加文件操作的错误检查
    所有fopen、fork、write操作都要检查返回值,避免在出错后继续执行引发更多问题。

修改后的代码示例

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

#define MAXIMA_LONGITUD_LINEA 256

int main(int argc, char *argv[]) {
    // 参数检查
    if (argc != 4) {
        fprintf(stderr, "Usage: %s <nombres> <apellidos> <output>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    // 打开输入输出文件并检查状态
    FILE *Nombres = fopen(argv[1], "r");
    if (!Nombres) {
        perror("Failed to open nombres file");
        exit(EXIT_FAILURE);
    }

    FILE *Apellidos = fopen(argv[2], "r");
    if (!Apellidos) {
        perror("Failed to open apellidos file");
        fclose(Nombres);
        exit(EXIT_FAILURE);
    }

    FILE *Output = fopen(argv[3], "w");
    if (!Output) {
        perror("Failed to open output file");
        fclose(Nombres);
        fclose(Apellidos);
        exit(EXIT_FAILURE);
    }

    char *tempname = "aaa.txt";
    FILE *Tmp = fopen(tempname, "w+");
    if (!Tmp) {
        perror("Failed to open temp file");
        fclose(Nombres);
        fclose(Apellidos);
        fclose(Output);
        exit(EXIT_FAILURE);
    }

    char linea[MAXIMA_LONGITUD_LINEA];
    pid_t hijoid;

    while (fgets(linea, MAXIMA_LONGITUD_LINEA, Nombres) != NULL) {
        printf("%s", linea); // 避免额外换行,linea已包含原文件的换行符

        // 关闭旧的临时文件指针,释放描述符
        if (Tmp) {
            fclose(Tmp);
        }
        // 重新打开临时文件,清空内容
        Tmp = fopen(tempname, "w+");
        if (!Tmp) {
            perror("Failed to re-open temp file");
            fclose(Nombres);
            fclose(Apellidos);
            fclose(Output);
            exit(EXIT_FAILURE);
        }

        hijoid = fork();
        if (hijoid == -1) {
            perror("Fork failed");
            fclose(Nombres);
            fclose(Apellidos);
            fclose(Output);
            fclose(Tmp);
            exit(EXIT_FAILURE);
        }

        if (hijoid == 0) {
            // 子进程关闭无关文件
            fclose(Nombres);
            fclose(Apellidos);
            fclose(Output);

            // 写入临时文件
            ssize_t written = write(fileno(Tmp), linea, strlen(linea));
            if (written == -1) {
                perror("Write to temp file failed");
                fclose(Tmp);
                exit(EXIT_FAILURE);
            }

            fclose(Tmp);
            exit(EXIT_SUCCESS);
        } else {
            // 父进程等待子进程完成
            int status;
            waitpid(hijoid, &status, 0);
            if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) {
                fprintf(stderr, "Child process exited with error\n");
            }

            rewind(Tmp);
            // 读取临时文件并写入输出
            if (fgets(linea, MAXIMA_LONGITUD_LINEA, Tmp)) {
                ssize_t written = write(fileno(Output), linea, strlen(linea));
                if (written == -1) {
                    perror("Write to output file failed");
                }
            } else {
                fprintf(stderr, "Failed to read line from temp file\n");
            }
        }
    }

    // 清理所有资源
    fclose(Nombres);
    fclose(Apellidos);
    fclose(Output);
    if (Tmp) {
        fclose(Tmp);
    }

    return EXIT_SUCCESS;
}

内容的提问来源于stack exchange,提问作者Neon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 04:38:10