Skip to content

C 错误处理

与其他一些语言(例如 C++、Java)不同,C 语言没有内置的异常处理机制。相反,错误处理主要依赖于检查函数的返回值,有时还会检查一个全局错误编号变量 errno。

许多标准库函数,特别是那些处理系统资源(如文件 I/O)或内存分配的函数,在发生错误时会返回一个特殊值(通常是 NULL、-1 或文档中针对特定函数说明的其他值)来指示错误。当错误发生时,这些函数通常会设置全局整数变量 errno(定义在 <errno.h> 中)为一个特定的错误代码,以提供更多详细信息。

重要规则:务必首先检查函数的返回值,看看是否发生了错误。只有在返回值指示错误发生时,才应该检查 errno 来理解错误发生的原因。有些函数即使成功也可能返回一个值,该值可能被误认为是错误指示符(例如 -1),因此检查返回值是至关重要的。

使用 errno 时,包含 <errno.h> 是良好的编程习惯。请注意,在现代多线程环境中,errno 是线程本地的,这意味着每个线程都有自己的 errno。

C 语言提供了两个标准函数来获取与 errno 值对应的可读性强的错误消息:

  • 函数 perror(const char *s)(来自 <stdio.h>)打印您提供的自定义消息 (s),后跟一个冒号、一个空格以及当前 errno 值的文本表示。输出会发送到标准错误流 (stderr)。
  • 函数 strerror(int errnum)(来自 <string.h>)返回一个指向字符串的指针,该字符串包含传递的错误代码 errnum 的文本表示。您通常会将 errno 的当前值传递给它。

示例:模拟文件打开错误:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h> // Required for EXIT_FAILURE
// Note: No need to declare 'extern int errno;' explicitly when including <errno.h>
// 注意:包含 <errno.h> 后无需显式声明 'extern int errno;' 了
int main(void) { // Use standard main signature
FILE *pf;
int errnum;
// Attempt to open a non-existent file for reading
// 尝试打开一个不存在的文件进行读取
pf = fopen("nonexistent_file.txt", "rb");
// ALWAYS check the return value first!
// 总是先检查返回值!
if (pf == NULL) {
errnum = errno; // Store errno immediately, as other calls might change it
// 立即存储 errno 的值,因为其他调用可能会改变它
fprintf(stderr, "Error opening file.\n"); // Informative message
// 错误提示消息
// Method 1: Using errno directly (less user-friendly)
// 方法 1:直接使用 errno(用户友好性较低)
fprintf(stderr, "Value of errno: %d\n", errnum);
// Method 2: Using perror (recommended for simple messages)
// 方法 2:使用 perror(推荐用于简单消息)
perror("Error reported by perror");
// Method 3: Using strerror (useful for custom formatting)
// 方法 3:使用 strerror(适用于自定义格式)
fprintf(stderr, "Error opening file: %s\n", strerror(errnum));
return EXIT_FAILURE; // Indicate failure using standard macro
// 使用标准宏指示失败
} else {
printf("File opened successfully! Closing it.\n");
// 文件打开成功!正在关闭它。
fclose(pf);
}
return EXIT_SUCCESS; // Indicate success using standard macro
// 使用标准宏指示成功
}

编译并执行时,假设 nonexistent_file.txt 不存在,输出将类似于(具体的 errno 值可能因操作系统而略有不同):

Error opening file.
Value of errno: 2
Error reported by perror: No such file or directory
Error opening file: No such file or directory

在 C 语言中,整数除以零会导致未定义行为。程序可能崩溃或产生意想不到的结果。浮点数除以零由 IEEE 754 标准定义(结果为无穷大或 NaN),但这通常仍被视为错误情况。

您必须在执行除法之前明确检查除数是否为零:

#include <stdio.h>
#include <stdlib.h> // For exit() and EXIT_FAILURE
// 用于 exit() 和 EXIT_FAILURE
int main(void) {
int dividend = 20;
int divisor = 0;
int quotient;
// Check before dividing
// 在除法前检查
if (divisor == 0) {
fprintf(stderr, "Error: Division by zero is not allowed.\n");
// 错误:不允许除以零。
return EXIT_FAILURE; // Exit indicating failure
// 退出并指示失败
}
// This part will only execute if divisor is non-zero
// 只有在除数非零时才会执行这部分代码
quotient = dividend / divisor;
printf("Value of quotient: %d\n", quotient); // Use printf for normal output
// 使用 printf 输出正常结果
return EXIT_SUCCESS; // Exit indicating success
// 退出并指示成功
}

编译并执行时,将产生以下输出:

Error: Division by zero is not allowed.

<assert.h> 头文件提供了 assert 宏。它用于在开发过程中检查应该始终为真的条件。如果 assert() 中的条件为假,程序会向 stderr 打印一条错误消息(包含文件名、行号和条件),然后终止。

assert 主要是一个调试工具。通过在包含 <assert.h> 之前定义 NDEBUG 宏(通常通过编译器标志如 -DNDEBUG 完成),可以在发布版本中禁用断言,从而移除任何性能开销。

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
// Disable assertions by uncommenting the next line or using -DNDEBUG
// 通过取消注释下一行或使用 -DNDEBUG 来禁用断言
// #define NDEBUG
int main(void) {
int divisor = 0;
// This assertion will fail
// 这个断言将失败
assert(divisor != 0);
// This line will not be reached if assertions are enabled
// 如果断言被启用,这行代码将不会被执行
printf("Continuing execution (only if NDEBUG is defined).\n");
// 继续执行(仅当 NDEBUG 定义时)
return EXIT_SUCCESS;
}

在启用断言(默认设置)的情况下运行此代码,可能会产生如下输出:

assertion_example: main.c:12: main: Assertion `divisor != 0' failed.

当程序完成时,它会向操作系统返回一个退出状态。此状态指示程序是成功完成还是遇到了错误。<stdlib.h> 中定义了用于此目的的标准宏:

  • EXIT_SUCCESS:通常定义为 0。表示成功执行。
  • EXIT_FAILURE:通常定义为一个非零值(通常为 1)。表示程序由于错误而终止。

在 main 函数的 return 语句或 exit() 调用中,使用这些宏比直接使用 0 或 -1 等“魔法数字”更能使您的程序更具可移植性和可读性。

使用标准退出码的示例:

#include <stdio.h>
#include <stdlib.h> // Required for EXIT_SUCCESS, EXIT_FAILURE
// EXIT_SUCCESS, EXIT_FAILURE 需要包含此头文件
int main(void) {
int dividend = 20;
int divisor = 5;
int quotient;
if (divisor == 0) {
fprintf(stderr, "Error: Division by zero!\n");
// 错误:除以零!
return EXIT_FAILURE; // Use standard macro for failure
// 使用标准宏指示失败
}
quotient = dividend / divisor;
printf("Value of quotient: %d\n", quotient);
// 商的值:%d
return EXIT_SUCCESS; // Use standard macro for success
// 使用标准宏指示成功
}

编译并执行时(divisor = 5),将产生以下输出:

Value of quotient: 4