C 语言错误处理
C语言中的错误处理主要依赖于返回值、全局变量和标准库提供的错误处理机制。
1. 基本的错误处理机制
1.1 返回值检查
这是最常用的错误处理方式,函数通过返回值来指示成功或失败。
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 示例:文件操作错误处理
void file_operation_example() {
FILE *file = fopen("nonexistent.txt", "r");
if (file == NULL) {
printf("错误:无法打开文件\n");
return;
}
// 文件操作...
fclose(file);
}
// 示例:内存分配错误处理
void memory_allocation_example() {
int *array = (int*)malloc(1000000000 * sizeof(int)); // 可能失败
if (array == NULL) {
printf("错误:内存分配失败\n");
return;
}
// 使用数组...
free(array);
}
// 示例:字符串操作错误处理
void string_operation_example() {
char source[] = "Hello, World!";
char destination[10];
// 安全的字符串复制
if (strncpy(destination, source, sizeof(destination) - 1) == NULL) {
printf("错误:字符串复制失败\n");
return;
}
destination[sizeof(destination) - 1] = '\0'; // 确保终止
printf("复制结果: %s\n", destination);
}
2. 标准错误处理机制
2.1 errno 全局变量
C标准库提供了 errno 全局变量来记录错误代码。
c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h> // 包含errno的定义
#include <string.h> // 包含strerror()
int main() {
FILE *file;
// 尝试打开不存在的文件
file = fopen("nonexistent_file.txt", "r");
if (file == NULL) {
printf("打开文件失败!\n");
printf("错误代码: %d\n", errno);
printf("错误描述: %s\n", strerror(errno));
}
// 尝试除以零
errno = 0; // 重置errno
int result = 1 / 0;
if (errno != 0) {
printf("数学运算错误!\n");
printf("错误代码: %d\n", errno);
printf("错误描述: %s\n", strerror(errno));
}
return 0;
}
2.2 perror() 函数
perror() 自动将 errno 转换为可读的错误信息。
c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main() {
FILE *file;
// 使用perror输出错误信息
file = fopen("/invalid/path/file.txt", "r");
if (file == NULL) {
perror("打开文件失败");
// 输出: 打开文件失败: No such file or directory
}
// 内存分配错误
int *ptr = malloc(1000000000000ULL);
if (ptr == NULL) {
perror("内存分配失败");
// 输出: 内存分配失败: Cannot allocate memory
} else {
free(ptr);
}
return 0;
}
3. 常见的错误代码
c
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <math.h>
void demonstrate_common_errors() {
printf("常见错误代码及其含义:\n");
printf("======================\n");
// 模拟各种错误
errno = EPERM; printf("EPERM (%d): %s\n", EPERM, strerror(EPERM));
errno = ENOENT; printf("ENOENT (%d): %s\n", ENOENT, strerror(ENOENT));
errno = EINTR; printf("EINTR (%d): %s\n", EINTR, strerror(EINTR));
errno = EIO; printf("EIO (%d): %s\n", EIO, strerror(EIO));
errno = EBADF; printf("EBADF (%d): %s\n", EBADF, strerror(EBADF));
errno = EAGAIN; printf("EAGAIN (%d): %s\n", EAGAIN, strerror(EAGAIN));
errno = ENOMEM; printf("ENOMEM (%d): %s\n", ENOMEM, strerror(ENOMEM));
errno = EACCES; printf("EACCES (%d): %s\n", EACCES, strerror(EACCES));
errno = EFAULT; printf("EFAULT (%d): %s\n", EFAULT, strerror(EFAULT));
errno = EINVAL; printf("EINVAL (%d): %s\n", EINVAL, strerror(EINVAL));
}
int main() {
demonstrate_common_errors();
return 0;
}
4. 自定义错误处理系统
4.1 定义错误码和错误处理函数
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 自定义错误码
typedef enum {
SUCCESS = 0,
ERROR_NULL_POINTER,
ERROR_INVALID_INPUT,
ERROR_OUT_OF_MEMORY,
ERROR_FILE_IO,
ERROR_NETWORK,
ERROR_CALCULATION
} ErrorCode;
// 错误信息映射
const char* error_messages[] = {
"操作成功",
"空指针错误",
"无效输入",
"内存不足",
"文件I/O错误",
"网络错误",
"计算错误"
};
// 错误处理函数
void handle_error(ErrorCode code, const char* context) {
if (code != SUCCESS) {
fprintf(stderr, "错误 [%d]: %s - %s\n",
code, error_messages[code], context);
// 根据错误严重程度决定是否退出
if (code == ERROR_OUT_OF_MEMORY || code == ERROR_NULL_POINTER) {
fprintf(stderr, "严重错误,程序退出\n");
exit(EXIT_FAILURE);
}
}
}
// 示例函数:安全的除法
ErrorCode safe_divide(double a, double b, double *result) {
if (result == NULL) {
return ERROR_NULL_POINTER;
}
if (b == 0.0) {
return ERROR_CALCULATION;
}
*result = a / b;
return SUCCESS;
}
// 示例函数:安全的字符串复制
ErrorCode safe_string_copy(char *dest, const char *src, size_t dest_size) {
if (dest == NULL || src == NULL) {
return ERROR_NULL_POINTER;
}
if (dest_size == 0) {
return ERROR_INVALID_INPUT;
}
strncpy(dest, src, dest_size - 1);
dest[dest_size - 1] = '\0'; // 确保字符串终止
return SUCCESS;
}
int main() {
double result;
ErrorCode err;
// 测试安全除法
err = safe_divide(10.0, 2.0, &result);
handle_error(err, "除法运算");
if (err == SUCCESS) {
printf("10.0 / 2.0 = %.2f\n", result);
}
err = safe_divide(10.0, 0.0, &result);
handle_error(err, "除以零");
// 测试安全字符串复制
char buffer[10];
err = safe_string_copy(buffer, "Hello, World!", sizeof(buffer));
handle_error(err, "字符串复制");
if (err == SUCCESS) {
printf("复制结果: %s\n", buffer);
}
return 0;
}
4.2 带错误上下文的结构体
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// 错误信息结构体
typedef struct {
int code;
char message[256];
char file[128];
int line;
time_t timestamp;
} ErrorInfo;
// 全局错误信息
ErrorInfo last_error = {0};
// 设置错误信息
void set_error(int code, const char* message, const char* file, int line) {
last_error.code = code;
strncpy(last_error.message, message, sizeof(last_error.message) - 1);
strncpy(last_error.file, file, sizeof(last_error.file) - 1);
last_error.line = line;
last_error.timestamp = time(NULL);
}
// 获取错误信息
void print_last_error() {
if (last_error.code != 0) {
char time_str[64];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S",
localtime(&last_error.timestamp));
fprintf(stderr, "[%s] 错误 %d: %s\n", time_str,
last_error.code, last_error.message);
fprintf(stderr, "位置: %s:%d\n", last_error.file, last_error.line);
}
}
// 清除错误信息
void clear_error() {
last_error.code = 0;
last_error.message[0] = '\0';
last_error.file[0] = '\0';
last_error.line = 0;
}
// 带错误检查的内存分配
void* checked_malloc(size_t size, const char* file, int line) {
void *ptr = malloc(size);
if (ptr == NULL) {
set_error(1, "内存分配失败", file, line);
}
return ptr;
}
// 带错误检查的文件打开
FILE* checked_fopen(const char* filename, const char* mode,
const char* file, int line) {
FILE *f = fopen(filename, mode);
if (f == NULL) {
char message[256];
snprintf(message, sizeof(message), "无法打开文件: %s", filename);
set_error(2, message, file, line);
}
return f;
}
// 宏简化错误检查调用
#define CHECKED_MALLOC(size) checked_malloc(size, __FILE__, __LINE__)
#define CHECKED_FOPEN(filename, mode) checked_fopen(filename, mode, __FILE__, __LINE__)
int main() {
clear_error(); // 清除之前的错误
// 使用带错误检查的函数
int *data = (int*)CHECKED_MALLOC(1000000000000ULL * sizeof(int));
if (data == NULL) {
print_last_error();
} else {
free(data);
}
FILE *file = CHECKED_FOPEN("nonexistent.txt", "r");
if (file == NULL) {
print_last_error();
} else {
fclose(file);
}
return 0;
}
5. 资源管理和错误处理
5.1 使用 goto 进行错误清理
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *name;
int *scores;
FILE *log_file;
} Student;
int initialize_student(Student *student, const char *name, int score_count) {
int status = -1;
// 初始化指针为NULL,便于清理
student->name = NULL;
student->scores = NULL;
student->log_file = NULL;
// 分配姓名
student->name = malloc(strlen(name) + 1);
if (student->name == NULL) {
perror("分配姓名内存失败");
goto cleanup;
}
strcpy(student->name, name);
// 分配分数数组
student->scores = malloc(score_count * sizeof(int));
if (student->scores == NULL) {
perror("分配分数数组失败");
goto cleanup;
}
// 打开日志文件
student->log_file = fopen("student.log", "w");
if (student->log_file == NULL) {
perror("打开日志文件失败");
goto cleanup;
}
// 初始化成功
status = 0;
printf("学生 %s 初始化成功\n", name);
cleanup:
if (status != 0) {
// 清理已分配的资源
free(student->name);
free(student->scores);
if (student->log_file != NULL) {
fclose(student->log_file);
}
printf("学生初始化失败\n");
}
return status;
}
void cleanup_student(Student *student) {
free(student->name);
free(student->scores);
if (student->log_file != NULL) {
fclose(student->log_file);
}
printf("学生资源已清理\n");
}
int main() {
Student student;
if (initialize_student(&student, "张三", 5) == 0) {
// 使用学生对象...
printf("正在使用学生: %s\n", student.name);
// 清理资源
cleanup_student(&student);
}
return 0;
}
5.2 使用 setjmp 和 longjmp 进行错误恢复
c
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <string.h>
jmp_buf error_recovery;
typedef enum {
ERR_NONE,
ERR_MEMORY,
ERR_FILE,
ERR_NETWORK
} ErrorType;
// 模拟可能失败的函数
int risky_operation(int scenario) {
switch (scenario) {
case 0:
return 0; // 成功
case 1:
printf("内存分配失败\n");
longjmp(error_recovery, ERR_MEMORY);
case 2:
printf("文件操作失败\n");
longjmp(error_recovery, ERR_FILE);
case 3:
printf("网络连接失败\n");
longjmp(error_recovery, ERR_NETWORK);
default:
return 0;
}
}
void handle_error(ErrorType error) {
switch (error) {
case ERR_MEMORY:
printf("处理内存错误: 尝试减少内存使用\n");
break;
case ERR_FILE:
printf("处理文件错误: 检查文件权限和路径\n");
break;
case ERR_NETWORK:
printf("处理网络错误: 检查网络连接\n");
break;
default:
printf("未知错误\n");
}
}
int main() {
ErrorType error;
// 设置错误恢复点
if ((error = (ErrorType)setjmp(error_recovery)) == ERR_NONE) {
printf("开始执行风险操作...\n");
// 模拟不同的场景
for (int i = 0; i < 4; i++) {
printf("\n场景 %d:\n", i);
risky_operation(i);
printf("场景 %d 执行成功\n", i);
}
printf("\n所有操作完成!\n");
} else {
printf("\n捕获到错误,进行错误恢复...\n");
handle_error(error);
printf("错误恢复完成,继续执行...\n");
}
return 0;
}
6. 信号处理
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
jmp_buf segmentation_fault_recovery;
// 段错误信号处理函数
void segmentation_fault_handler(int sig) {
printf("\n捕获到段错误信号 (%d)\n", sig);
printf("进行错误恢复...\n");
// 跳转到恢复点
longjmp(segmentation_fault_recovery, 1);
}
// 浮点错误处理函数
void floating_point_handler(int sig) {
printf("\n捕获到浮点错误信号 (%d)\n", sig);
printf("处理浮点异常...\n");
// 恢复默认处理并重新发送信号
signal(sig, SIG_DFL);
raise(sig);
}
int main() {
// 设置信号处理函数
signal(SIGSEGV, segmentation_fault_handler); // 段错误
signal(SIGFPE, floating_point_handler); // 浮点异常
printf("信号处理示例\n");
printf("============\n");
// 段错误恢复示例
if (setjmp(segmentation_fault_recovery) == 0) {
printf("1. 段错误恢复测试:\n");
int *bad_pointer = NULL;
// 这会导致段错误,但会被我们的处理函数捕获
// *bad_pointer = 42; // 取消注释来测试
printf("段错误测试通过\n");
} else {
printf("从段错误中恢复成功\n");
}
// 浮点错误示例
printf("\n2. 浮点错误测试:\n");
// 这会导致浮点异常
// int result = 1 / 0; // 取消注释来测试
printf("浮点错误测试通过\n");
printf("\n程序正常结束\n");
return 0;
}
7. 断言(Assertions)
c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
// 使用断言进行调试
double calculate_discriminant(double a, double b, double c) {
// 前置条件检查
assert(!isnan(a) && !isnan(b) && !isnan(c)); // 确保不是NaN
assert(isfinite(a) && isfinite(b) && isfinite(c)); // 确保是有限数
double discriminant = b * b - 4 * a * c;
// 后置条件检查
assert(!isnan(discriminant)); // 结果不应为NaN
assert(isfinite(discriminant)); // 结果应为有限数
return discriminant;
}
// 自定义断言宏
#define CUSTOM_ASSERT(condition, message) \
do { \
if (!(condition)) { \
fprintf(stderr, "断言失败: %s\n", message); \
fprintf(stderr, "文件: %s, 行号: %d\n", __FILE__, __LINE__); \
abort(); \
} \
} while(0)
void test_custom_assert() {
int *ptr = malloc(sizeof(int));
CUSTOM_ASSERT(ptr != NULL, "内存分配失败");
CUSTOM_ASSERT(*ptr == 0, "新分配的内存应该被初始化为0"); // 这不一定成立!
free(ptr);
}
int main() {
printf("断言示例\n");
printf("========\n");
// 标准断言测试
printf("1. 标准断言测试:\n");
double disc = calculate_discriminant(1.0, -3.0, 2.0);
printf("判别式: %.2f\n", disc);
// 这会触发断言失败
// double bad_disc = calculate_discriminant(0.0, 0.0, 0.0); // 取消注释测试
// 自定义断言测试
printf("\n2. 自定义断言测试:\n");
test_custom_assert();
printf("\n所有测试通过!\n");
return 0;
}
8. 完整的错误处理框架示例
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
// 错误级别
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARNING,
LOG_ERROR,
LOG_CRITICAL
} LogLevel;
// 错误处理配置
typedef struct {
LogLevel min_level;
FILE *log_file;
int exit_on_critical;
} ErrorConfig;
// 全局错误处理配置
ErrorConfig error_config = {
.min_level = LOG_WARNING,
.log_file = NULL,
.exit_on_critical = 1
};
// 初始化错误处理系统
int error_system_init(const char *log_filename, LogLevel min_level) {
if (log_filename != NULL) {
error_config.log_file = fopen(log_filename, "a");
if (error_config.log_file == NULL) {
perror("无法打开日志文件");
return -1;
}
} else {
error_config.log_file = stderr;
}
error_config.min_level = min_level;
return 0;
}
// 清理错误处理系统
void error_system_cleanup() {
if (error_config.log_file != NULL && error_config.log_file != stderr) {
fclose(error_config.log_file);
error_config.log_file = NULL;
}
}
// 记录错误
void log_message(LogLevel level, const char *file, int line, const char *format, ...) {
if (level < error_config.min_level) {
return;
}
// 获取当前时间
time_t now = time(NULL);
char time_str[64];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", localtime(&now));
// 级别名称
const char *level_names[] = {
"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
};
// 格式化消息
va_list args;
va_start(args, format);
fprintf(error_config.log_file, "[%s] %s: %s:%d: ",
time_str, level_names[level], file, line);
vfprintf(error_config.log_file, format, args);
fprintf(error_config.log_file, "\n");
fflush(error_config.log_file);
va_end(args);
// 关键错误处理
if (level == LOG_CRITICAL && error_config.exit_on_critical) {
fprintf(error_config.log_file, "关键错误,程序退出\n");
error_system_cleanup();
exit(EXIT_FAILURE);
}
}
// 日志宏
#define LOG_DEBUG(...) log_message(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_INFO(...) log_message(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_WARNING(...) log_message(LOG_WARNING, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_ERROR(...) log_message(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_CRITICAL(...) log_message(LOG_CRITICAL, __FILE__, __LINE__, __VA_ARGS__)
// 示例应用程序
int main() {
// 初始化错误处理系统
if (error_system_init("app.log", LOG_DEBUG) != 0) {
fprintf(stderr, "错误处理系统初始化失败\n");
return EXIT_FAILURE;
}
LOG_INFO("应用程序启动");
// 模拟各种级别的日志
LOG_DEBUG("这是调试信息");
LOG_INFO("应用程序初始化完成");
LOG_WARNING("内存使用率较高");
LOG_ERROR("文件读取失败");
// 模拟关键错误(会退出程序)
// LOG_CRITICAL("数据库连接失败"); // 取消注释测试
LOG_INFO("应用程序正常退出");
// 清理
error_system_cleanup();
return EXIT_SUCCESS;
}
总结
C语言错误处理的主要方法:
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 返回值检查 | 函数调用、资源分配 | 简单直观 | 错误信息有限 |
| errno全局变量 | 系统调用、库函数 | 标准化的错误代码 | 线程不安全 |
| perror() | 快速错误报告 | 自动格式化错误信息 | 灵活性差 |
| 自定义错误码 | 应用程序特定错误 | 可读性好,类型安全 | 需要额外定义 |
| setjmp/longjmp | 错误恢复、嵌套调用 | 非局部跳转 | 复杂,可能泄漏资源 |
| 信号处理 | 系统级错误、异常 | 处理异步事件 | 平台依赖性强 |
| 断言 | 调试、开发阶段 | 自动检查假设 | 发布版本中通常被禁用 |