PAYATHON 2026

C 文件写入时,ferror() 的错误指示器会一直保留吗?

数据小陈

可以集中检查流错误:流的错误指示器一旦被设置,后续成功的 fprintf() 调用不会自动清除它。符合 C 标准的 fprintf() 实现也不能在内部调用 clearerr() 来清除此前的错误状态。

不过,clearerr() 不是唯一能清除错误状态的操作。写入缓冲区的数据也可能直到刷新或关闭文件时才发生输出错误,因此仍要检查 fclose() 的返回值。

错误指示器会保留到什么时候?

ferror(f) 检查流当前的错误指示器:返回非零值表示指示器已设置,返回零表示未设置。调用 ferror() 不会清除该状态,它返回的也不是具体错误码。

对于普通的连续写入,可以这样理解:

  • 某次写入发生流错误,错误指示器被设置。
  • 后续写入即使成功,也不会清除此前的错误指示器。
  • 最后调用 ferror(),仍能观察到这个错误状态。

有些操作会清除状态:clearerr() 会清除错误指示器和文件结束指示器;rewind() 会清除错误指示器;freopen() 重新打开流时也会清除相关指示器。因此,集中检查的前提是中间没有执行这些操作,也没有其他代码通过同一个流清除错误状态。

原来的检查方式是否可行?

对于流的写入错误,这个思路可行:先保存 ferror() 的结果,再关闭文件并检查关闭结果。

下面的函数采用了这个顺序。原示例中的 fopen() 语句末尾还缺少一个分号:

#include <stdio.h>

int write_file(int x, const char *s)
{
    FILE *f = fopen("file.txt", "w");
    if (f == NULL) {
        return -1;
    }

    fprintf(f, "foo %d\n", x);
    fprintf(f, "bar %s\n", s);

    int failed = ferror(f) != 0;

    if (fclose(f) == EOF) {
        failed = 1;
    }

    return failed ? -1 : 0;
}

这里假定 s 指向有效的、以空字符结尾的字符串。

原代码中的 fclose(f) != 0 || err 也会先执行 fclose(),不会因为 err 非零而跳过关闭操作。如果调换成 err || fclose(f) != 0,则可能因短路求值而漏掉 fclose()。

无论 fclose() 成功还是失败,关闭后都不能再用这个 FILE * 调用 ferror()。

如果必须在关闭之前检测错误

fprintf() 可能只是把数据放进标准 I/O 缓冲区。此时 ferror() 返回零,说明流尚未记录到错误,但不能说明缓冲区中的数据已经成功输出。

如果需要在关闭前主动输出缓冲数据,可以在最后一次写入后调用 fflush(),再检查错误状态:

int failed = 0;

if (fflush(f) == EOF) {
    failed = 1;
}

if (ferror(f)) {
    failed = 1;
}

if (fclose(f) == EOF) {
    failed = 1;
}

if (failed) {
    handle_error();
}

即使 fflush() 或 ferror() 已经报告错误,也要执行 fclose()。显式刷新成功后,仍应检查关闭操作的返回值。

fflush() 成功表示标准 I/O 层的刷新完成,不等于数据已经持久保存到物理存储设备。

ferror() 能代替所有 fprintf() 返回值检查吗?

对于已经设置到流上的读写错误,可以集中使用 ferror() 检查。但如果要捕获 fprintf() 的所有失败情况,就不能只依赖它。

fprintf() 在输出错误或编码错误时会返回负值。涉及字符编码转换的失败,例如某些 %ls 转换失败,不能仅凭流错误指示器保证被发现。存在这类转换时,应同时检查 fprintf() 的返回值。可以累计失败状态,再统一处理:

if (fprintf(f, "foo %d\n", x) < 0) {
    failed = 1;
}

对于问题中展示的 %d 和 %s,在参数有效且没有其他未定义行为的前提下,重点是保留已有的流错误,并检查最终刷新或关闭时产生的错误。

备注:内容仅供参考。