80T资源合集下载
链接:https://pan.quark.cn/s/5643428d4f9f

在编程世界里,我们都喜欢一帆风顺。但现实是,程序总会因为各种原因出错:文件不存在、权限不足、磁盘已满……一个只会默默失败的程序是可怕的,它让你像个无头苍蝇一样去猜错因。而一个专业的程序,则应该在失败时清晰地“告诉”你,它到底遭遇了什么。

今天,我们就来学习如何让我们的 C 程序“学会说话”,掌握 Linux 提供的两大错误处理利器:​​perror​​ 和 ​​strerror​​。

一、沉默的失败者:一个反面教材

让我们从一个简单的文件拷贝程序开始。这个版本只做了最基础的返回值检查,如果出错,它只会打印一句含糊不清的“Error!”。

// naive_cp.c (一个不擅长表达的程序)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Usage: %s <source> <destination>\n", argv[0]);
        exit(1);
    }

    int fd_src = open(argv[1], O_RDONLY);
    if (fd_src == -1) {
        printf("Error opening source file!\n"); // 太模糊了!
        exit(1);
    }

    // ... 后续读写逻辑 ...

    close(fd_src);
    printf("Operation might be successful...?\n");
    return 0;
}

现在,让我们故意制造一些错误场景:

场景 1:源文件不存在

gcc naive_cp.c -o naive_cp
./naive_cp non_existent_file.txt out.txt

运行结果:

Error opening source file!

它只告诉我们错了,但为什么错?是文件不存在?还是权限问题?我们无从得知。

二、神秘的信使:全局变量 ​​errno​

当 Linux 的系统调用(如 ​​open​​, ​​read​​)执行失败时,它们除了返回 ​​-1​​ 之外,还会做一件非常重要的事情:将一个全局整型变量 ​​errno​​ 设置为一个特定的值,用以表示具体的错误类型。

  • errno​​:定义在 ​​<errno.h>​​ 头文件中。
  • 错误码:每一个 ​​errno​​ 的值都对应一个宏定义,如 ​​ENOENT​​ (No such file or directory)、​​EACCES​​ (Permission denied) 等。

​errno​​ 就是内核传递给我们的“错误情报”。现在的问题是,我们如何解读这份情报?

三、利器一:​​perror​​ —— 简单直接的错误播报员

​perror​​ 是最简单、最直接的错误报告工具。

  • 函数原型: ​​void perror(const char *s);​
  • 头文件: ​​<stdio.h>​
  • 工作方式:
  1. 它会先打印你传入的字符串 ​​s​​。
  2. 然后自动加上一个冒号和一个空格 ​​": "​​。
  3. 最后,它会根据当前 ​​errno​​ 的值,打印出对应的、人类可读的错误描述信息。
  4. 所有内容都输出到标准错误流 (stderr)

升级我们的程序 (perror_cp.c​):

// perror_cp.c (学会使用 perror)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h> // 虽然 perror 会自动用,但明确包含是个好习惯

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <source> <destination>\n", argv[0]);
        exit(1);
    }

    int fd_src = open(argv[1], O_RDONLY);
    if (fd_src == -1) {
        // 使用 perror!
        perror("open source file failed"); 
        exit(1);
    }
    
    printf("Source file opened successfully, fd = %d\n", fd_src);
    close(fd_src);
    return 0;
}

再次进行错误场景测试:

场景 1:源文件不存在

gcc perror_cp.c -o perror_cp
./perror_cp non_existent_file.txt out.txt

运行结果:

open source file failed: No such file or directory

看!信息变得无比清晰!我们自定义的 "open source file failed" 后面,​​perror​​ 自动追加了根本原因:"No such file or directory"。

场景 2:没有权限读取文件

touch secret.txt
chmod 000 secret.txt # 移除所有权限
./perror_cp secret.txt out.txt

运行结果:

open source file failed: Permission denied

同样精准地指出了是“权限被拒绝”的问题。

四、利器二:​​strerror​​ —— 灵活定制的错误翻译家

​perror​​ 很方便,但它只能直接打印到 ​​stderr​​。如果我们想把错误信息记录到日志文件,或者格式化成 JSON 发送到监控系统呢?这时,​​strerror​​ 就派上用场了。

  • 函数原型: ​​char *strerror(int errnum);​
  • 头文件: ​​<string.h>​
  • 工作方式: 你将 ​​errno​​ 的值作为参数传给它,它会返回一个指向错误描述字符串的指针。

使用 strerror​ 进一步升级程序 (​strerror_cp.c​):

// strerror_cp.c (学会使用 strerror)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h> // for strerror

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <source> <destination>\n", argv[0]);
        exit(1);
    }

    int fd_src = open(argv[1], O_RDONLY);
    if (fd_src == -1) {
        // 使用 strerror,配合 fprintf 灵活输出
        fprintf(stderr, "[FATAL ERROR] Failed to open '%s'. Reason: %s (errno: %d)\n", 
                argv[1], strerror(errno), errno);
        exit(1);
    }
    
    printf("Source file '%s' opened successfully, fd = %d\n", argv[1], fd_src);
    close(fd_src);
    return 0;
}

进行同样的错误场景测试:

场景 1:源文件不存在

gcc strerror_cp.c -o strerror_cp
./strerror_cp non_existent_file.txt out.txt

运行结果:

[FATAL ERROR] Failed to open 'non_existent_file.txt'. Reason: No such file or directory (errno: 2)

我们不仅得到了错误描述,还完全自定义了输出格式,甚至打印出了原始的 ​​errno​​ 值 (2 对应 ​​ENOENT​​),这对于调试和日志记录非常有用。

五、​​perror​​ vs. ​​strerror​​:如何选择?

特性

​perror​

​strerror​

功能

打印自定义前缀 + 错误描述

返回错误描述字符串

输出位置

固定输出到 ​​stderr​

由你决定(​​printf​​​, ​​fprintf​​​, ​​log_...​​)

易用性

非常简单,一行搞定

稍复杂,需要配合 ​​printf​​ 家族函数

适用场景

简单的命令行工具,快速诊断

需要灵活格式化、记录日志、GUI 弹窗等复杂场景

一句话总结:想快速在终端看错误,用 perror​;想对错误信息做进一步处理,用 ​strerror​

结语

编写健壮的程序,就像建造一栋坚固的建筑,地基必须扎实。在 Linux 系统编程中,对每一个可能失败的系统调用进行返回值检查,并使用 perror​ 或 ​strerror​ 清晰地报告错误,就是这个不可或缺的地基。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐