C 语言文件错误处理
更新时间:2026-08-26。本文是
languages/c/主题入门层第 65 篇。文件操作能失败:文件不存在、权限不足、磁盘满、读取中途出错——这些不是"代码 bug",是外部环境问题,程序必须能发现并体面处理。perror告诉你为什么失败,ferror/feof告诉你读取的状态,还有那个被无数人用错的feof。
本文要回答的问题
- 怎么拿到"失败的具体原因"?
feof为什么容易用错?正确姿势是什么?- 文件出错后还能继续读吗?
一、errno 与 perror:失败的原因
C 标准库用全局变量 errno 记录最后一次错误码,perror 把它转成人类可读信息:
c
#include <stdio.h>
#include <errno.h>
FILE *fp = fopen("missing.txt", "r");
if (fp == NULL) {
perror("fopen"); // fopen: No such file or directory
printf("errno = %d\n", errno); // 2
return 1;
}常见 errno:
| errno | 含义 | 场景 |
|---|---|---|
ENOENT (2) | 文件不存在 | 路径写错 |
EACCES (13) | 权限不足 | 只读目录写文件 |
ENOSPC (28) | 磁盘满 | 写入失败 |
EISDIR (21) | 是目录 | 试图当文件打开 |
strerror(errno) 也能拿到描述字符串,适合拼进日志。
二、ferror:读写过程中出错
fread/fwrite/fgetc 返回失败值,可能是读到结尾(正常),也可能是出错(异常)——用 ferror 区分:
c
int c;
while ((c = fgetc(fp)) != EOF) {
/* 处理 */
}
if (ferror(fp)) { // EOF 是出错还是读完?
perror("read error"); // 真出错了
} else {
printf("normal EOF\n"); // 正常读完
}ferror(fp) 返回非零 = 上次操作出错,出错标志会一直保持直到 clearerr 或文件操作重置。
三、feof 陷阱:别用它判断循环结束
经典错误写法:
c
// ❌ 教科书级错误
while (!feof(fp)) {
fgets(buf, sizeof(buf), fp); // 读失败时 feof 才置位
/* 处理 buf */ // 最后一次循环 buf 是垃圾!
}为什么错:feof 只有在读操作失败后才被置位——读到最后一个字符时 feof 还是 0,循环多跑一次,处理一次无效数据。
c
// ✅ 正确:用读函数的返回值判断
while (fgets(buf, sizeof(buf), fp) != NULL) {
/* 处理 buf */
}铁律:用读函数的返回值判断结束,feof 只用来"确认"是正常结尾(配合 ferror 区分错误)。
四、错误恢复:出错后怎么办
| 情况 | 策略 |
|---|---|
| fopen 失败 | 报错返回,不继续 |
| 读到一半出错 | 停止读取,报告已读多少 |
| 写入失败(磁盘满) | 立即停止,别让程序"以为写成功了" |
| 可重试的失败 | clearerr(fp) 后重试(少用) |
文件操作错误大多不可恢复(文件没了、磁盘满了),体面的做法是:报告清楚(文件、行号、原因)、停止操作、返回错误码,别继续用错误的指针/数据蒙混。
五、防御式编程清单
c
FILE *fp = fopen(path, "r");
if (fp == NULL) { // 1. 打开必判空
fprintf(stderr, "open %s: %s\n", path, strerror(errno));
return -1;
}
/* 读操作... */
if (ferror(fp)) { // 2. 结束后查错误
perror("read");
fclose(fp);
return -1;
}
fclose(fp); // 3. 关闭(成功路径)三层防线:打开判空 → 操作看返回值 → 结束后查 ferror。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 用 feof 判断结束 | 多处理一次垃圾数据 | 用读函数返回值 |
| 只看 EOF 不问原因 | 出错当正常结尾 | 配 ferror 区分 |
| 忘检查写入 | 磁盘满静默 | 关键写检查返回值 |
| 错误后继续用 fp | 数据错乱 | 出错即停 |
| 不打印原因 | 排障靠猜 | perror/strerror |
七、与本站主线衔接
- fopen 模式与判空,见fopen 与 fclose;
- 读取循环的正确写法,见字符与行读取;
- errno 的更广泛应用,见errno 与错误处理。
一句话总结
perror/strerror 报告失败原因,ferror 区分"出错"与"正常读完",feof 只做确认别做循环判断——三层防线(打开判空、操作看返回值、结束查 ferror)把文件错误挡在门外——文件 IO 是"外部世界"的接口,永远假设它会失败。