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 全局变量 定义与使用 #include <errno.h> 检查返回值 → 读errno 常见errno值 EINVAL, ENOMEM EACCES, ENOENT perror() 函数 自动拼接 + 打印到stderr 自带换行符 strerror() 函数 返回错误描述字符串 灵活处理,可存可打印 核心原则:先检查返回值,再读errno

这张图把errno的四个核心知识点串在了一起。你可以看到,errno本身只是一个「信号灯」,真正干活的是perror()和strerror()这两个函数。

🔑 核心要点:

  • errno是全局变量,函数调用失败后会设置它
  • 永远用宏定义(如ENOMEM)而不是硬编码数值
  • perror()适合快速调试,strerror()适合精细控制
  • 多线程环境下考虑使用strerror_r()

好了,errno的内容就讲到这里。记住一句话:检查返回值,再读errno。这个习惯养成了,你的C代码会健壮很多。

可可精品资料库