Appearance
错误处理
在C语言编程中,错误处理是确保程序健壮性和可靠性的重要部分。C语言提供了多种机制来检测、报告和处理错误。
错误处理的基本概念
C语言中的错误处理主要包括:
- 返回值检查:函数返回特定值表示错误
- errno变量:全局错误码变量
- 断言:用于调试的错误检测
- 错误处理函数:如
perror()和strerror()
返回值检查
许多C标准库函数通过返回值来指示错误。
常见返回值约定
| 函数类型 | 成功返回值 | 错误返回值 |
|---|---|---|
| 整数函数 | 非负值 | -1或负值 |
| 指针函数 | 有效指针 | NULL |
| 布尔函数 | 非0(真) | 0(假) |
示例:文件操作错误处理
c
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp = fopen("nonexistent.txt", "r");
// 检查文件是否成功打开
if (fp == NULL) {
printf("无法打开文件\n");
return 1;
}
fclose(fp);
return 0;
}示例:内存分配错误处理
c
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int*)malloc(1000000000 * sizeof(int));
// 检查内存分配是否成功
if (arr == NULL) {
printf("内存分配失败\n");
return 1;
}
printf("内存分配成功\n");
free(arr);
return 0;
}示例:数学函数错误处理
c
#include <stdio.h>
#include <math.h>
#include <errno.h>
int main() {
double result = sqrt(-1.0);
// 检查数学函数错误
if (errno == EDOM) {
printf("数学错误:参数超出定义域\n");
}
return 0;
}errno变量
errno是一个全局变量,用于存储最近一次系统调用的错误码。定义在<errno.h>头文件中。
常见errno值
| errno值 | 描述 |
|---|---|
EDOM | 数学参数超出定义域 |
ERANGE | 结果超出范围 |
EACCES | 权限被拒绝 |
ENOENT | 文件或目录不存在 |
ENOMEM | 内存不足 |
EINVAL | 无效参数 |
使用errno
c
#include <stdio.h>
#include <errno.h>
#include <math.h>
int main() {
errno = 0; // 重置errno
double result = sqrt(-1.0);
if (errno != 0) {
printf("错误发生,errno = %d\n", errno);
}
return 0;
}perror函数
perror()函数将errno对应的错误信息输出到标准错误流。
c
void perror(const char *s);示例:使用perror
c
#include <stdio.h>
#include <errno.h>
int main() {
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
fclose(fp);
return 0;
}输出示例:
打开文件失败: No such file or directorystrerror函数
strerror()函数将错误码转换为可读的错误信息字符串。
c
char *strerror(int errnum);示例:使用strerror
c
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main() {
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
printf("错误: %s\n", strerror(errno));
return 1;
}
fclose(fp);
return 0;
}示例:显示所有错误码
c
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main() {
for (int i = 0; i < 50; i++) {
printf("errno %d: %s\n", i, strerror(i));
}
return 0;
}exit函数
exit()函数用于正常终止程序,可以返回状态码给操作系统。
c
void exit(int status);EXIT_SUCCESS(0):程序成功执行EXIT_FAILURE(1):程序执行失败
示例:使用exit
c
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
fprintf(stderr, "无法打开文件\n");
exit(EXIT_FAILURE);
}
fclose(fp);
exit(EXIT_SUCCESS);
}assert宏
assert宏用于调试,在条件为假时终止程序并显示错误信息。
c
void assert(int expression);示例:使用assert
c
#include <stdio.h>
#include <assert.h>
int main() {
int x = 10;
int y = 0;
// 断言y不为0
assert(y != 0);
int result = x / y;
printf("结果: %d\n", result);
return 0;
}输出示例:
Assertion failed: y != 0, file test.c, line 9禁用assert
在发布版本中,可以通过定义NDEBUG宏来禁用assert。
c
#define NDEBUG
#include <assert.h>自定义错误处理
错误码定义
c
#include <stdio.h>
#include <stdlib.h>
#define SUCCESS 0
#define ERROR_FILE_NOT_FOUND 1
#define ERROR_MEMORY_ALLOC 2
#define ERROR_INVALID_PARAM 3
int readFile(const char *filename, char **buffer) {
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
return ERROR_FILE_NOT_FOUND;
}
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
*buffer = (char*)malloc(size + 1);
if (*buffer == NULL) {
fclose(fp);
return ERROR_MEMORY_ALLOC;
}
fread(*buffer, 1, size, fp);
(*buffer)[size] = '\0';
fclose(fp);
return SUCCESS;
}
int main() {
char *buffer = NULL;
int result = readFile("data.txt", &buffer);
if (result != SUCCESS) {
switch (result) {
case ERROR_FILE_NOT_FOUND:
printf("错误:文件未找到\n");
break;
case ERROR_MEMORY_ALLOC:
printf("错误:内存分配失败\n");
break;
default:
printf("未知错误\n");
}
return 1;
}
printf("文件内容: %s\n", buffer);
free(buffer);
return 0;
}错误处理函数
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef enum {
ERR_NONE = 0,
ERR_FILE_NOT_FOUND,
ERR_MEMORY_ALLOC,
ERR_INVALID_PARAM,
ERR_DIVISION_BY_ZERO
} ErrorCode;
const char* getErrorMessage(ErrorCode err) {
switch (err) {
case ERR_NONE: return "无错误";
case ERR_FILE_NOT_FOUND: return "文件未找到";
case ERR_MEMORY_ALLOC: return "内存分配失败";
case ERR_INVALID_PARAM: return "无效参数";
case ERR_DIVISION_BY_ZERO: return "除零错误";
default: return "未知错误";
}
}
void handleError(ErrorCode err) {
if (err != ERR_NONE) {
fprintf(stderr, "错误: %s\n", getErrorMessage(err));
exit(EXIT_FAILURE);
}
}
ErrorCode divide(int a, int b, int *result) {
if (b == 0) {
return ERR_DIVISION_BY_ZERO;
}
*result = a / b;
return ERR_NONE;
}
int main() {
int result;
ErrorCode err = divide(10, 0, &result);
handleError(err);
printf("结果: %d\n", result);
return 0;
}文件操作错误处理
完整的文件操作示例
c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main() {
const char *filename = "data.txt";
// 打开文件
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
fprintf(stderr, "无法打开文件 %s: %s\n",
filename, strerror(errno));
return EXIT_FAILURE;
}
// 读取文件
char buffer[256];
if (fgets(buffer, sizeof(buffer), fp) == NULL) {
if (ferror(fp)) {
fprintf(stderr, "读取文件时出错: %s\n", strerror(errno));
fclose(fp);
return EXIT_FAILURE;
}
}
printf("读取内容: %s\n", buffer);
// 关闭文件
if (fclose(fp) != 0) {
fprintf(stderr, "关闭文件时出错: %s\n", strerror(errno));
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}内存分配错误处理
安全的内存分配
c
#include <stdio.h>
#include <stdlib.h>
void* safeMalloc(size_t size) {
void *ptr = malloc(size);
if (ptr == NULL) {
fprintf(stderr, "内存分配失败: 请求 %zu 字节\n", size);
exit(EXIT_FAILURE);
}
return ptr;
}
void* safeCalloc(size_t num, size_t size) {
void *ptr = calloc(num, size);
if (ptr == NULL) {
fprintf(stderr, "内存分配失败: 请求 %zu 个 %zu 字节\n", num, size);
exit(EXIT_FAILURE);
}
return ptr;
}
void* safeRealloc(void *ptr, size_t size) {
void *new_ptr = realloc(ptr, size);
if (new_ptr == NULL && size > 0) {
fprintf(stderr, "内存重新分配失败: 请求 %zu 字节\n", size);
free(ptr);
exit(EXIT_FAILURE);
}
return new_ptr;
}
int main() {
int *arr = (int*)safeMalloc(10 * sizeof(int));
arr = (int*)safeRealloc(arr, 20 * sizeof(int));
free(arr);
return 0;
}数学运算错误处理
安全的除法运算
c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <errno.h>
double safeDivide(double a, double b) {
if (b == 0.0) {
fprintf(stderr, "错误:除零错误\n");
exit(EXIT_FAILURE);
}
return a / b;
}
double safeSqrt(double x) {
if (x < 0.0) {
fprintf(stderr, "错误:负数不能开平方\n");
exit(EXIT_FAILURE);
}
return sqrt(x);
}
double safeLog(double x) {
if (x <= 0.0) {
fprintf(stderr, "错误:对数的参数必须大于0\n");
exit(EXIT_FAILURE);
}
return log(x);
}
int main() {
double result;
result = safeDivide(10.0, 2.0);
printf("10.0 / 2.0 = %.2f\n", result);
result = safeSqrt(16.0);
printf("sqrt(16.0) = %.2f\n", result);
result = safeLog(10.0);
printf("log(10.0) = %.2f\n", result);
return 0;
}用户输入错误处理
安全的整数输入
c
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int readInt(const char *prompt) {
char buffer[100];
int value;
int success;
while (1) {
printf("%s", prompt);
if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
fprintf(stderr, "输入错误\n");
exit(EXIT_FAILURE);
}
success = sscanf(buffer, "%d", &value);
if (success == 1) {
return value;
} else {
printf("无效输入,请输入一个整数\n");
}
}
}
int readPositiveInt(const char *prompt) {
int value;
while (1) {
value = readInt(prompt);
if (value > 0) {
return value;
}
printf("请输入一个正整数\n");
}
}
int main() {
int age = readPositiveInt("请输入您的年龄: ");
printf("您的年龄: %d\n", age);
return 0;
}安全的字符串输入
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void readString(const char *prompt, char *buffer, int size) {
printf("%s", prompt);
if (fgets(buffer, size, stdin) == NULL) {
fprintf(stderr, "输入错误\n");
exit(EXIT_FAILURE);
}
// 移除换行符
size_t len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
}
}
int main() {
char name[50];
readString("请输入您的姓名: ", name, sizeof(name));
printf("您好, %s!\n", name);
return 0;
}错误处理最佳实践
1. 始终检查返回值
c
// 好的做法
FILE *fp = fopen("file.txt", "r");
if (fp == NULL) {
// 处理错误
}
// 不好的做法
FILE *fp = fopen("file.txt", "r");
// 没有检查fp是否为NULL2. 及时释放资源
c
#include <stdio.h>
#include <stdlib.h>
int processFile(const char *filename) {
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
return -1;
}
char *buffer = (char*)malloc(1024);
if (buffer == NULL) {
fclose(fp); // 释放已分配的资源
return -1;
}
// 处理文件...
free(buffer);
fclose(fp);
return 0;
}3. 使用有意义的错误信息
c
// 好的做法
fprintf(stderr, "错误:无法打开配置文件 %s: %s\n",
config_file, strerror(errno));
// 不好的做法
fprintf(stderr, "错误\n");4. 区分错误类型
c
#include <stdio.h>
#include <errno.h>
void handleFileError(const char *filename) {
if (errno == ENOENT) {
fprintf(stderr, "文件 %s 不存在\n", filename);
} else if (errno == EACCES) {
fprintf(stderr, "没有权限访问文件 %s\n", filename);
} else {
fprintf(stderr, "文件错误: %s\n", strerror(errno));
}
}5. 使用goto进行错误清理
c
#include <stdio.h>
#include <stdlib.h>
int process() {
FILE *fp1 = NULL;
FILE *fp2 = NULL;
int *data = NULL;
fp1 = fopen("file1.txt", "r");
if (fp1 == NULL) {
goto error;
}
fp2 = fopen("file2.txt", "r");
if (fp2 == NULL) {
goto error;
}
data = (int*)malloc(100 * sizeof(int));
if (data == NULL) {
goto error;
}
// 处理数据...
free(data);
fclose(fp2);
fclose(fp1);
return 0;
error:
if (data) free(data);
if (fp2) fclose(fp2);
if (fp1) fclose(fp1);
return -1;
}总结
C语言的错误处理机制虽然不如某些高级语言那样完善,但通过合理使用以下技术,可以编写出健壮的程序:
- 返回值检查:检查函数返回值判断是否成功
- errno变量:使用全局错误码获取详细错误信息
- perror和strerror:将错误码转换为可读信息
- assert宏:用于调试时的断言检查
- 自定义错误处理:设计适合应用的错误处理机制
- 资源管理:确保在错误发生时正确释放资源
良好的错误处理习惯能够提高程序的可靠性和可维护性,使程序在面对异常情况时能够优雅地处理。