2. errno全局变量
说到C语言的错误处理,errno绝对是个绕不开的话题。我个人习惯把它叫做「错误码的快递员」——它负责把系统底层发生的错误信息,传递到你的代码里。
很多初学者觉得errno用起来麻烦,其实不然。你想想看,C语言不像Java有try-catch,也没有Python那种优雅的异常机制。那怎么办?errno就是C语言给出的答案。虽然原始,但足够实用。
2.1 errno的定义与使用
errno本质上是一个全局整型变量。它在<errno.h>中定义,你可以把它理解成一个「公共告示板」——任何函数发现错误,都会在这个告示板上贴一个数字标签。
#include <stdio.h>
#include <errno.h>
int main() {
FILE *fp = fopen("不存在的文件.txt", "r");
if (fp == NULL) {
printf("错误码: %d\n", errno);
}
return 0;
}
运行这段代码,你会看到输出类似「错误码: 2」。嗯,2代表什么?别急,我们马上讲到。
⚠️ 重要提醒:errno的值只在函数调用失败时才有意义。如果函数执行成功,errno的值不会被清零。我曾经见过有人这么写:
// 错误示范
fopen("文件.txt", "r");
if (errno != 0) {
// 这里可能误判!
}
正确的做法是:先检查函数返回值,再判断errno。
2.2 常见的errno值
errno的值有很多,但常用的就那么几个。我整理了一张表,方便你查阅:
| 宏定义 | 数值 | 含义 | 典型场景 |
|---|---|---|---|
| EINVAL | 22 | 无效参数 | 传入了非法参数,比如给sqrt()传负数 |
| ENOMEM | 12 | 内存不足 | malloc()分配失败时最常见 |
| EACCES | 13 | 权限不足 | 试图打开一个没有读权限的文件 |
| ENOENT | 2 | 文件或目录不存在 | fopen()打开不存在的文件 |
| EBUSY | 16 | 设备或资源忙 | 试图打开一个已被占用的设备 |
这些数值在不同操作系统上可能不一样。比如Linux和Windows的ENOMEM值就不同。所以永远不要直接写if (errno == 12),而是用if (errno == ENOMEM)。这是我在跨平台开发中踩过的坑,分享给你。
2.3 perror()函数:最省事的错误打印
perror()是我个人最喜欢的错误处理函数。它简单粗暴——直接把errno对应的错误信息打印到stderr上。
#include <stdio.h>
#include <errno.h>
int main() {
FILE *fp = fopen("不存在的文件.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
// 输出: 打开文件失败: No such file or directory
}
return 0;
}
你看,perror()会自动拼接你传入的字符串和错误描述。省去了手动查表的麻烦。我在调试网络程序时特别喜欢用它——出错了直接看stderr,一目了然。
💡 小技巧:perror()输出的信息会包含换行符,所以不用自己加\n。另外,它输出到stderr而不是stdout,这样即使你重定向了标准输出,错误信息依然能显示在终端上。
2.4 strerror()函数:更灵活的错误信息获取
如果你不想把错误信息直接打印到stderr,而是想存到字符串里做进一步处理,那就用strerror()。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main() {
FILE *fp = fopen("不存在的文件.txt", "r");
if (fp == NULL) {
char *err_msg = strerror(errno);
printf("错误详情: %s\n", err_msg);
// 输出: 错误详情: No such file or directory
}
return 0;
}
strerror()返回一个指向错误描述字符串的指针。这个字符串是静态分配的,所以不要试图去free它。另外,在多线程环境下要小心——有些实现不是线程安全的。不过现在大多数系统都提供了strerror_r()作为线程安全版本。
2.5 知识体系总览
说了这么多,我们来画张图,把errno的整个知识体系串起来:
这张图把errno的四个核心知识点串在了一起。你可以看到,errno本身只是一个「信号灯」,真正干活的是perror()和strerror()这两个函数。
🔑 核心要点:
- errno是全局变量,函数调用失败后会设置它
- 永远用宏定义(如ENOMEM)而不是硬编码数值
- perror()适合快速调试,strerror()适合精细控制
- 多线程环境下考虑使用strerror_r()
好了,errno的内容就讲到这里。记住一句话:检查返回值,再读errno。这个习惯养成了,你的C代码会健壮很多。
可可精品资料库