在 C 语言中,错误处理通常依靠一些专门负责"运行时出错"的函数:它们把错误码或错误信息返回给程序员,用来提示某个操作失败或行为异常。
由于 C 不像 Java、Python 等高级语言那样提供内建的异常机制(例如 try-catch),所有的错误检查都只能手动完成,主要手段包括:
- 查看函数返回值(常见
-1
、NULL
); - 读取全局变量
errno
; - 调用
perror
、strerror
等系统接口获取可读的错误描述。
因此,代码里最常见的模式就是用一条 if
语句立刻判断这些返回值,一旦发现异常就立即处理。
什么是 errno
errno
是在 <errno.h>
中定义的一个全局变量 ,用来记录最近一次 C 标准库或系统调用出错的错误码 。当某个函数执行失败时,系统会把 errno
设成对应的整数值,程序通过读取这个值就能知道具体发生了什么错误。不同数值代表不同类型的问题,方便我们进行有针对性的错误处理。
示例代码
c
#include <errno.h>
#include <stdio.h>
int main() {
FILE *fp;
// 尝试打开一个不存在的文件
fp = fopen("gfg.txt", "r");
printf("errno 的值: %d\n", errno);
return 0;
}
运行结果
errno 的值: 2
常见 errno
值及其含义
errno 值 | 错误描述 |
---|---|
1 | 操作不允许(Operation not permitted) |
2 | 没有那个文件或目录(No such file or directory) |
3 | 没有那个进程(No such process) |
4 | 系统调用被中断(Interrupted system call) |
5 | I/O 错误(I/O error) |
6 | 没有那个设备或地址(No such device or address) |
7 | 参数列表太长(Argument list too long) |
8 | 执行格式错误(Exec format error) |
9 | 文件描述符无效(Bad file number) |
10 | 没有子进程(No child processes) |
11 | 资源暂时不可用,请重试(Try again) |
12 | 内存不足(Out of memory) |
13 | 权限不足(Permission denied) |
不同的Error处理方式
C 语言里针对不同错误类型有各种常用处理方式,下面列出几种典型做法:
1. 用 if-else 判断
C 没有 try-catch,只能靠手动检查。最常见的就是用 if-else 看函数返回值,发现不对劲就处理。
示例:
c
#include <errno.h>
#include <stdio.h>
int main() {
FILE *fp;
// 尝试以只读方式打开一个不存在的文件
fp = fopen("gfg.txt", "r");
if (fp == NULL) {
printf("File opening error");
} else {
printf("File opened successfully");
}
return 0;
}
运行结果:
arduino
File opening error
解释: 程序用 fopen
打开文件,返回 NULL
说明失败,于是通过 if-else
给出提示;否则提示打开成功。
2. 使用 perror()
perror()
会把 当前 errno
对应的错误文本 输出到标准错误流(stderr
)。它先打印你给的自定义前缀,再跟一段系统自带的错误描述,方便快速定位问题。
示例:
c
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main() {
FILE *fp;
// 尝试打开一个不存在的文件,errno 会被设置
fp = fopen("gfg.txt", "r");
// 先打印 errno 数值
printf("errno 的值: %d\n", errno);
// 再用 perror 输出可读的错误原因
perror("perror 提示");
return 0;
}
运行结果:
yaml
errno 的值: 2
perror 提示: No such file or directory
解释: fopen
失败会把 errno
设为 2
;perror
根据这个值自动给出"没有那个文件或目录"的提示,并把你给的字符串"perror 提示"放在前面,一目了然。
3. 使用 strerror()
strerror()
会把当前 errno
对应的错误文本以字符串形式返回 ,而不是直接打印。你可以把它保存、拼接或按需输出,灵活性比 perror()
更高。
示例:
c
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main() {
FILE *fp;
// 尝试打开一个不存在的文件,errno 被设置
fp = fopen("gfg.txt", "r");
// 打印 errno 数值和对应的错误描述
printf("errno 的值: %d\n", errno);
printf("错误信息: %s\n", strerror(errno));
return 0;
}
运行结果:
makefile
errno 的值: 2
错误信息: No such file or directory
解释: strerror(errno)
根据错误码 2
返回字符串 "No such file or directory"
,我们用 printf
自行决定输出格式,比 perror()
更自由。
4. 使用 ferror()
专门用于检查文件操作
ferror()
用来检测文件操作是否出错 。只要对 FILE *
流发生过任何失败(读、写、定位等),它就返回非 0 值;没有错误则返回 0。适合在"做完一系列文件动作"后统一检查。
示例:
c
#include <stdio.h>
int main() {
FILE *fptr = fopen("gfg.txt", "w");
if (fptr == NULL) {
perror("打开文件失败");
return 1;
}
// 向文件写入数据
fprintf(fptr, "Hello, GFG!");
// 检查写入过程是否出错
if (ferror(fptr) == 0)
printf("数据写入成功。\n");
else
printf("写入时发生错误。\n");
fclose(fptr);
return 0;
}
运行结果:
数据写入成功。
解释: 程序先打开文件并写入字符串,随后调用 ferror()
判断写入阶段有没有产生错误;返回 0 表示一切正常,于是提示"数据写入成功"。
5. 使用 feof()
专门用于检查文件读到文件末尾
feof()
用来检测"读文件时是否已经读到文件末尾" 。当上一次读操作遇到 EOF 标志后,它返回非 0 ;否则返回 0。常与 fgetc
、fgets
等配合,避免多读一次。
示例:
c
#include <stdio.h>
int main() {
FILE *fp = fopen("gfg.txt", "r");
if (fp == NULL)
return 0;
while (1) {
char c = fgetc(fp); // 一次读一个字符
if (feof(fp)) // 检查是否已读到文件末尾
break;
printf("%c", c);
}
fclose(fp);
return 0;
}
文件 gfg.txt
内容:
css
Welcome to GeeksforGeeks
运行结果:
css
Welcome to GeeksforGeeks
解释: 循环逐字符读取,当 fgetc
遇到文件结束符后,feof(fp)
返回真,循环立即跳出,从而正确停止读取,不会多输出一个垃圾字符。
6. 使用 clearerr()
专门进行文件读写操作时手动清除错误标志
clearerr()
用来手动清除流上的"错误标志"和"EOF 标志"。一旦调用,流就恢复到"无错且未到达末尾"的状态,可以继续读写,避免因为上一次操作留下的标志而导致后续函数提前失败。
示例:
c
#include <stdio.h>
int main() {
FILE *fptr = fopen("gfg.txt", "w+"); // 读写模式
if (!fptr) return 0;
fprintf(fptr, "GeeksForGeeks!"); // 写入数据
rewind(fptr); // 把读写指针拉回开头
while (fgetc(fptr) != EOF); // 读到文件末尾,EOF 标志被置位
if (feof(fptr)) {
printf("EOF 已到达\n");
}
clearerr(fptr); // 手动清除 EOF 标志
if (!feof(fptr)) {
printf("EOF 已成功重置\n");
}
fclose(fptr);
return 0;
}
运行结果:
EOF 已到达
EOF 已成功重置
解释: 读完文件后 feof()
返回真;调用 clearerr()
后,EOF 标志被清零,feof()
立即变假,流可以再次读写,而不会因为"残留 EOF"导致后续操作直接失败。
7. 退出状态(Exit Status)
C 程序通过 exit()
函数主动结束运行,并向操作系统返回一个"状态码"。C 标准在 <stdlib.h>
里定义了两个宏:
EXIT_SUCCESS
------ 表示程序成功结束EXIT_FAILURE
------ 表示程序异常结束
调用 exit()
之后,后续代码不会再执行。
示例:
c
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
FILE *fp;
// 以二进制只读方式打开一个不存在的文件
fp = fopen("gfg.txt", "rb");
if (fp == NULL) {
printf("errno 值: %d\n", errno);
printf("打开文件失败: %s\n", strerror(errno));
perror("perror 提示");
// 以"失败"状态退出
exit(EXIT_FAILURE);
// 以下语句不会被执行
printf("我不会被打印\n");
}
else {
fclose(fp);
// 以"成功"状态退出
exit(EXIT_SUCCESS);
// 同样不会执行
printf("我不会被打印\n");
}
return 0;
}
运行结果:
yaml
errno 值: 2
打开文件失败: No such file or directory
perror 提示: No such file or directory
解释: 文件打开失败时,程序通过 errno
、strerror()
和 perror()
输出详细错误信息,随后调用 exit(EXIT_FAILURE)
立即终止,并告诉操作系统"执行失败"。如果文件打开成功,则关闭文件并以 exit(EXIT_SUCCESS)
正常结束。
8. 不依赖库函数的错误处理
前面介绍的都是借助 C 标准库提供的手段(errno
、perror
、strerror
等)。
其实还有很多错误完全可以用普通代码自己拦截,例如:
- 除零
- 输入合法性
- 文件打开失败
- 数组越界
只要提前做条件判断,就能避免崩溃或未定义行为。
示例:手动防止除零
c
#include <stdio.h>
int main() {
int num1 = 10, num2 = 0;
if (num2 == 0)
printf("错误:不允许除零\n");
else
printf("结果:%d\n", num1 / num2);
return 0;
}
运行结果:
错误:不允许除零
解释: 在真正做除法前,先判断除数是否为 0;若是,则直接给出提示并跳过运算,从而完全避免了除零异常。