Skip to content

错误处理

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

错误处理的基本概念

C语言中的错误处理主要包括:

  1. 返回值检查:函数返回特定值表示错误
  2. errno变量:全局错误码变量
  3. 断言:用于调试的错误检测
  4. 错误处理函数:如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 directory

strerror函数

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是否为NULL

2. 及时释放资源

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语言的错误处理机制虽然不如某些高级语言那样完善,但通过合理使用以下技术,可以编写出健壮的程序:

  1. 返回值检查:检查函数返回值判断是否成功
  2. errno变量:使用全局错误码获取详细错误信息
  3. perror和strerror:将错误码转换为可读信息
  4. assert宏:用于调试时的断言检查
  5. 自定义错误处理:设计适合应用的错误处理机制
  6. 资源管理:确保在错误发生时正确释放资源

良好的错误处理习惯能够提高程序的可靠性和可维护性,使程序在面对异常情况时能够优雅地处理。