36、C语言中的错误处理

厨子大约 4 分钟C语言基础程序程序厨

错误处理是确保程序健壮性和稳定性的重要部分。C语言提供了多种机制来检测、报告和处理错误。

错误码与返回值

在C语言中,许多函数通过返回值来指示操作是否成功。**通常,函数的返回值为0表示成功,非零值表示错误。**这些非零值通常被称为错误码,用于表示不同类型的错误。

注意,返回0表示成功,非0表示失败。

示例代码

#include <stdio.h>

// 定义一个简单的函数,尝试进行除法运算,并返回错误码
int divide(int a, int b, int *result) {
    if (b == 0) {
        return -1; // 除数为0的错误码
    }
    *result = a / b;
    return 0; // 成功
}

int main() {
    int result;
    int errorCode = divide(10, 0, &result); // 尝试除以0
    if (errorCode != 0) {
        printf("Error: Division by zero.\n");
    } else {
        printf("Result: %d\n", result);
    }
    return 0;
}

示例代码中,divide函数尝试进行除法运算。如果除数为0,则返回-1作为错误码。在main函数中,我们检查divide函数的返回值,并根据错误码进行相应的处理。

标准错误处理机制

C语言标准库提供了一套标准错误处理机制,主要包括全局变量errnoperror函数和strerror函数。

  • errnoerrno是一个全局变量,用于表示最近一次函数调用的错误类型。标准库中的许多函数在发生错误时会设置errno
  • perrorperror函数用于打印描述错误信息的字符串。它会根据errno的值打印相应的错误信息,并在前面加上用户自定义的错误消息前缀。
  • strerrorstrerror函数将错误代码转换为对应的错误消息字符串。对于记录或显示错误信息非常有用。

示例代码

#include <stdio.h>
#include <errno.h>
#include <string.h>

int main() {
    FILE *file = fopen("nonexistent.txt", "r");
    if (file == NULL) {
        int errnum = errno;
        fprintf(stderr, "Error number: %d\n", errnum);
        perror("Error opening file");
        printf("Error opening file: %s\n", strerror(errnum));
    } else {
        // 执行文件操作
        fclose(file);
    }
    return 0;
}

输出(https://godbolt.org/z/dcxM1bzzz):open in new window

Error number: 2
Error opening file: No such file or directory
Error opening file: No such file or directory

上述代码中,我们尝试打开一个不存在的文件。由于文件不存在,fopen函数返回NULL,并且errno被设置为相应的错误代码。我们使用fprintfperrorstrerror函数来输出错误信息。

自定义错误处理函数

在复杂的大型项目中,统一的错误处理机制很有必要,它可以显著提高代码的可读性和可维护性。

通过自定义错误处理函数和自定义错误码,我们可以集中管理错误处理逻辑,避免重复代码。

示例代码

#include <stdio.h>
#include <stdlib.h>

// 自定义错误码
#define SUCCESS 0
#define ERROR_FILE_NOT_FOUND 1
#define ERROR_OUT_OF_MEMORY 2

// 自定义错误处理函数
void handleError(int errorCode, const char *message) {
    switch (errorCode) {
        case ERROR_FILE_NOT_FOUND:
            fprintf(stderr, "Error: %s - File not found.\n", message);
            break;
        case ERROR_OUT_OF_MEMORY:
            fprintf(stderr, "Error: %s - Out of memory.\n", message);
            break;
        default:
            fprintf(stderr, "Error: %s - Unknown error.\n", message);
            break;
    }
    exit(EXIT_FAILURE); // 终止程序
}

int main() {
    FILE *file = fopen("nonexistent.txt", "r");
    if (file == NULL) {
        handleError(ERROR_FILE_NOT_FOUND, "Failed to open file");
    }
    // 其他操作...
    fclose(file); // 注意:这里应该添加对file是否为NULL的检查,以避免在file为NULL时调用fclose导致崩溃
    return SUCCESS;
}

handleError函数接收一个错误码和一个错误消息字符串,并根据错误码打印相应的错误信息。然后,它使用exit函数终止程序。

在实际应用中,你可能希望在某些情况下不终止程序,而是返回错误码或进行其他恢复操作。

断言与调试技巧

断言是一种在程序中检查某个条件是否成立的方法。如果条件不成立,则会触发一个断言错误,并终止程序的执行。

断言通常用于调试过程中,帮助我们快速定位问题所在。

注意,断言仅会在Debug包中起作用。

示例代码

#include <assert.h>
#include <stdio.h>

int divide(int a, int b, int *result) {
    assert(b != 0); // 断言除数不为0
    *result = a / b;
    return 0;
}

int main() {
    int result;
    divide(10, 0, &result); // 这里会触发断言错误
    printf("Result: %d\n", result); // 这行代码不会被执行
    return 0;
}

输出(https://godbolt.org/z/37PKnY7z3):open in new window

output.s: /app/example.cpp:5: int divide(int, int, int *): Assertion `b != 0' failed.
Program terminated with signal: SIGSEGV

divide函数使用assert来检查除数b是否为0。如果b为0,则会触发断言错误,并终止程序的执行。

调试技巧

  • 使用调试器:如GDBLLDB,它允许你逐步执行代码、设置断点、检查变量值等。
  • 日志记录:在代码的关键位置添加日志,方便在程序运行时记录相关信息。有助于追踪程序的执行流程并定位问题所在。(在调试过程中和线上过程中,日志打印一定要详细,因为这是我们分析问题的最主要手段

练习

  1. 运行本文档中的所有代码,分析运行结果。