C 语言 errno
更新时间:2026-08-26。本文是
languages/c/主题入门层第 70 篇。C 的函数没有异常机制,错误怎么传递?约定是:返回错误值 + 设置全局变量errno说明原因。文件打不开是"不存在"还是"没权限"?errno告诉你。这一篇讲清 errno 的机制、常见取值和使用时最容易踩的三个坑。
本文要回答的问题
errno是什么?什么时候被设置?- 怎么把它转成可读的错误信息?
- 使用 errno 有哪些注意事项?
一、errno 是什么
errno 是一个线程局部的整型变量(现代 C 里是宏,展开成线程局部存储),标准库函数出错时设置它的值:
c
#include <stdio.h>
#include <errno.h>
FILE *fp = fopen("nofile.txt", "r");
if (fp == NULL) {
printf("errno = %d\n", errno); // 2(ENOENT,文件不存在)
}关键规则:
- 只有函数失败时 errno 才有意义——成功时它可能残留上次的值;
- errno 不会被函数自动清零——新调用失败会覆盖,但成功调用不保证清;
- 判断顺序:先看返回值(成功/失败),再看 errno(为什么失败)。
二、perror 与 strerror
c
FILE *fp = fopen("nofile.txt", "r");
if (fp == NULL) {
perror("fopen"); // fopen: No such file or directory
printf("%s\n", strerror(errno)); // No such file or directory
return 1;
}perror(前缀):打印前缀 + ": " + 错误描述到 stderr;strerror(errno):返回错误描述的字符串,方便拼进自定义日志。
三、常见 errno 值
| errno | 数值(Linux) | 含义 |
|---|---|---|
EPERM | 1 | 操作不被允许 |
ENOENT | 2 | 文件或目录不存在 |
EACCES | 13 | 权限不足 |
ENOSPC | 28 | 磁盘已满 |
EINVAL | 22 | 参数无效 |
ERANGE | 34 | 结果超出范围(如 strtol 溢出) |
ETIMEDOUT | 110 | 连接超时 |
永远用符号名(ENOENT)而不是数字——数值在不同平台可能不同,符号名是标准化的。
四、使用 errno 的注意事项
1. 调用前清零(需要区分"上一次错误"时):
c
errno = 0;
long v = strtol("abc", &end, 10);
if (errno == ERANGE) { ... } // 只有清零后才可信2. 失败函数的返回值检查优先:
c
int *p = malloc(1024);
if (p == NULL) {
if (errno == ENOMEM) { /* 内存不足 */ }
}3. 别把 errno 当布尔:if (errno) 不可靠——残留值会误报。
五、错误处理模式
c
int read_config(const char *path) {
FILE *fp = fopen(path, "r");
if (fp == NULL) {
fprintf(stderr, "open %s failed: %s\n",
path, strerror(errno)); // 具体原因
return -1;
}
/* ... */
fclose(fp);
return 0;
}模式总结:
- 返回值判断成败(NULL、-1、EOF);
- errno 说明细节(
strerror转文本); - 失败路径:报告 + 返回错误码,别继续用坏状态。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 成功时读 errno | 读到残留值 | 只看失败时 |
| 调用前不清零 | 误判溢出 | 关键调用前 errno=0 |
| 用数字而非常量名 | 跨平台错 | 用 ENOENT 等 |
| 只查 errno 不看返回值 | 逻辑错乱 | 返回值优先 |
| 忘记 #include <errno.h> | 编译错 | 头文件带上 |
七、与本站主线衔接
- 文件错误的完整处理流程,见文件错误处理;
- strtol 与 ERANGE 的配合,见stdlib.h 字符串转换;
- 系统调用的错误返回(更底层的 errno),见系统调用与返回码。
一句话总结
errno 是库函数失败时的"原因说明"(线程局部整型),用 perror/strerror 转成可读文本;铁律是——先看返回值判断成败、再看 errno 问原因、需要时调用前清零、永远用符号名——返回值告诉你"错了",errno 告诉你"为什么错"。