学而时习之:C语言中的Error处理

在 C 语言中,错误处理通常依靠一些专门负责"运行时出错"的函数:它们把错误码或错误信息返回给程序员,用来提示某个操作失败或行为异常。

由于 C 不像 Java、Python 等高级语言那样提供内建的异常机制(例如 try-catch),所有的错误检查都只能手动完成,主要手段包括:

  • 查看函数返回值(常见 -1NULL);
  • 读取全局变量 errno
  • 调用 perrorstrerror 等系统接口获取可读的错误描述。

因此,代码里最常见的模式就是用一条 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 设为 2perror 根据这个值自动给出"没有那个文件或目录"的提示,并把你给的字符串"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。常与 fgetcfgets 等配合,避免多读一次。

示例:

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

解释: 文件打开失败时,程序通过 errnostrerror()perror() 输出详细错误信息,随后调用 exit(EXIT_FAILURE) 立即终止,并告诉操作系统"执行失败"。如果文件打开成功,则关闭文件并以 exit(EXIT_SUCCESS) 正常结束。

8. 不依赖库函数的错误处理

前面介绍的都是借助 C 标准库提供的手段(errnoperrorstrerror 等)。

其实还有很多错误完全可以用普通代码自己拦截,例如:

  • 除零
  • 输入合法性
  • 文件打开失败
  • 数组越界

只要提前做条件判断,就能避免崩溃或未定义行为。


示例:手动防止除零

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;若是,则直接给出提示并跳过运算,从而完全避免了除零异常

相关推荐
qq_437896434 天前
unsigned 是等于 unsigned int
开发语言·c++·算法·c
Lonble5 天前
C语言篇:预处理
c语言·c
BlackQid7 天前
深入理解指针Part1——C语言
c++·c
Lonble19 天前
C语言篇:宏
c语言·c
Lonble19 天前
C语言篇:翻译阶段
c语言·c
空山新雨(大队长)1 个月前
C 语言第一课:hello word c
c++·c·exe
饭碗的彼岸one1 个月前
C++ 并发编程:异步任务
c语言·开发语言·c++·后端·c·异步
EleganceJiaBao1 个月前
我的创作纪念日
c
梁辰兴1 个月前
数据结构:排序
数据结构·算法·排序算法·c·插入排序·排序·交换排序