Skip to content

C 标准库 - <errno.h>

概述

<errno.h> 头文件定义了用于错误处理的宏和变量。当系统调用或库函数失败时,它们会设置一个整数错误代码,这个错误代码存储在 errno 变量中。

errno 变量

声明

c
int errno;

errno 是一个全局变量,用于存储最近一次函数调用失败时的错误代码。

使用规则

  1. 只在函数调用失败时检查 errno
  2. 在调用可能设置 errno 的函数之前,应该将其设置为 0
  3. errno 的值只在函数调用失败时才有意义

标准错误代码

EDOM

域错误(Domain Error),通常用于数学函数,当参数超出函数定义域时设置。

c
#include <stdio.h>
#include <math.h>
#include <errno.h>

int main() {
    errno = 0;
    double result = sqrt(-1.0);
    
    if (errno == EDOM) {
        printf("域错误: 参数超出函数定义域\n");
    }
    
    return 0;
}

ERANGE

范围错误(Range Error),当结果超出可表示范围时设置。

c
#include <stdio.h>
#include <math.h>
#include <errno.h>

int main() {
    errno = 0;
    double result = exp(1000.0);
    
    if (errno == ERANGE) {
        printf("范围错误: 结果超出可表示范围\n");
    }
    
    return 0;
}

EILSEQ

非法字节序列(Illegal Byte Sequence),通常用于多字节字符处理。

c
#include <stdio.h>
#include <errno.h>
#include <wchar.h>

int main() {
    errno = 0;
    wchar_t wc = L'A';
    mbtowc(&wc, "\xFF", 1);
    
    if (errno == EILSEQ) {
        printf("非法字节序列\n");
    }
    
    return 0;
}

系统相关错误代码

以下错误代码在 POSIX 系统中常见,但具体值可能因系统而异:

EPERM

操作不允许(Operation Not Permitted)

c
#include <stdio.h>
#include <errno.h>
#include <unistd.h>

int main() {
    if (setuid(0) == -1) {
        if (errno == EPERM) {
            printf("权限不足: 需要超级用户权限\n");
        }
    }
    return 0;
}

ENOENT

文件或目录不存在(No Such File or Directory)

c
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>

int main() {
    errno = 0;
    int fd = open("nonexistent.txt", O_RDONLY);
    
    if (fd == -1 && errno == ENOENT) {
        printf("文件不存在\n");
    }
    
    return 0;
}

EACCES

权限被拒绝(Permission Denied)

c
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>

int main() {
    errno = 0;
    int fd = open("/root/file.txt", O_RDONLY);
    
    if (fd == -1 && errno == EACCES) {
        printf("权限被拒绝\n");
    }
    
    return 0;
}

ENOMEM

内存不足(Out of Memory)

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

int main() {
    errno = 0;
    void* ptr = malloc(SIZE_MAX);
    
    if (ptr == NULL && errno == ENOMEM) {
        printf("内存不足\n");
    }
    
    return 0;
}

EINVAL

无效参数(Invalid Argument)

c
#include <stdio.h>
#include <errno.h>
#include <pthread.h>

int main() {
    pthread_t thread;
    errno = 0;
    
    if (pthread_create(&thread, NULL, NULL, NULL) != 0) {
        if (errno == EINVAL) {
            printf("无效参数\n");
        }
    }
    
    return 0;
}

错误处理函数

perror()

c
void perror(const char *s);

打印错误信息到标准错误流。

c
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>

int main() {
    int fd = open("nonexistent.txt", O_RDONLY);
    
    if (fd == -1) {
        perror("打开文件失败");
    }
    
    return 0;
}

输出:

打开文件失败: No such file or directory

strerror()

c
char *strerror(int errnum);

返回错误代码对应的描述字符串。

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

int main() {
    printf("EDOM: %s\n", strerror(EDOM));
    printf("ERANGE: %s\n", strerror(ERANGE));
    printf("ENOENT: %s\n", strerror(ENOENT));
    printf("ENOMEM: %s\n", strerror(ENOMEM));
    
    return 0;
}

strerror_r()

c
int strerror_r(int errnum, char *buf, size_t buflen);

线程安全的错误描述函数(POSIX 版本)。

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

int main() {
    char buf[256];
    int result = strerror_r(ENOENT, buf, sizeof(buf));
    
    if (result == 0) {
        printf("错误描述: %s\n", buf);
    }
    
    return 0;
}

实际应用示例

1. 文件操作错误处理

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

void safe_fopen(const char* filename, const char* mode) {
    FILE* file = fopen(filename, mode);
    
    if (file == NULL) {
        fprintf(stderr, "无法打开文件 '%s': %s\n", 
                filename, strerror(errno));
        return;
    }
    
    printf("文件 '%s' 打开成功\n", filename);
    fclose(file);
}

int main() {
    safe_fopen("test.txt", "r");
    safe_fopen("/root/protected.txt", "r");
    
    return 0;
}

2. 内存分配错误处理

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

void* safe_malloc(size_t size) {
    void* ptr = malloc(size);
    
    if (ptr == NULL) {
        fprintf(stderr, "内存分配失败: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    
    return ptr;
}

int main() {
    size_t size = 1024 * 1024 * 1024;  // 1GB
    
    void* ptr = safe_malloc(size);
    printf("内存分配成功: %p\n", ptr);
    
    free(ptr);
    return 0;
}

3. 数学函数错误处理

c
#include <stdio.h>
#include <math.h>
#include <errno.h>

double safe_sqrt(double x) {
    errno = 0;
    double result = sqrt(x);
    
    if (errno == EDOM) {
        fprintf(stderr, "错误: sqrt 的参数必须是非负数\n");
        return 0.0;
    }
    
    return result;
}

double safe_log(double x) {
    errno = 0;
    double result = log(x);
    
    if (errno == EDOM) {
        fprintf(stderr, "错误: log 的参数必须是正数\n");
        return 0.0;
    }
    
    return result;
}

int main() {
    printf("sqrt(16) = %f\n", safe_sqrt(16));
    printf("sqrt(-1) = %f\n", safe_sqrt(-1));
    
    printf("log(10) = %f\n", safe_log(10));
    printf("log(0) = %f\n", safe_log(0));
    
    return 0;
}

4. 网络编程错误处理

c
#include <stdio.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void handle_socket_error() {
    switch (errno) {
        case EACCES:
            fprintf(stderr, "权限不足\n");
            break;
        case EADDRINUSE:
            fprintf(stderr, "地址已被使用\n");
            break;
        case ECONNREFUSED:
            fprintf(stderr, "连接被拒绝\n");
            break;
        case ETIMEDOUT:
            fprintf(stderr, "连接超时\n");
            break;
        default:
            fprintf(stderr, "未知错误: %s\n", strerror(errno));
    }
}

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    if (sockfd == -1) {
        perror("创建套接字失败");
        handle_socket_error();
        return 1;
    }
    
    printf("套接字创建成功\n");
    close(sockfd);
    
    return 0;
}

5. 自定义错误处理

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

typedef enum {
    ERROR_NONE = 0,
    ERROR_FILE_NOT_FOUND,
    ERROR_PERMISSION_DENIED,
    ERROR_OUT_OF_MEMORY,
    ERROR_INVALID_ARGUMENT
} ErrorCode;

const char* get_error_message(ErrorCode code) {
    switch (code) {
        case ERROR_NONE:
            return "没有错误";
        case ERROR_FILE_NOT_FOUND:
            return "文件未找到";
        case ERROR_PERMISSION_DENIED:
            return "权限被拒绝";
        case ERROR_OUT_OF_MEMORY:
            return "内存不足";
        case ERROR_INVALID_ARGUMENT:
            return "无效参数";
        default:
            return "未知错误";
    }
}

void handle_error(ErrorCode code) {
    if (code != ERROR_NONE) {
        fprintf(stderr, "错误: %s\n", get_error_message(code));
        if (errno != 0) {
            fprintf(stderr, "系统错误: %s\n", strerror(errno));
        }
    }
}

int main() {
    handle_error(ERROR_FILE_NOT_FOUND);
    handle_error(ERROR_NONE);
    
    return 0;
}

错误处理最佳实践

1. 始终检查返回值

c
// 好的做法
FILE* file = fopen("test.txt", "r");
if (file == NULL) {
    perror("打开文件失败");
    return 1;
}

// 不好的做法
FILE* file = fopen("test.txt", "r");
// 没有检查返回值

2. 重置 errno

c
// 好的做法
errno = 0;
double result = sqrt(x);
if (errno == EDOM) {
    // 处理错误
}

// 不好的做法
double result = sqrt(x);
if (errno == EDOM) {
    // errno 可能包含之前的错误值
}

3. 使用有意义的错误信息

c
// 好的做法
if (fd == -1) {
    fprintf(stderr, "无法打开配置文件 '%s': %s\n", 
            config_file, strerror(errno));
}

// 不好的做法
if (fd == -1) {
    perror("错误");
}

4. 区分不同的错误类型

c
// 好的做法
if (fd == -1) {
    switch (errno) {
        case ENOENT:
            fprintf(stderr, "配置文件不存在\n");
            break;
        case EACCES:
            fprintf(stderr, "没有权限访问配置文件\n");
            break;
        default:
            fprintf(stderr, "打开配置文件失败: %s\n", strerror(errno));
    }
}

// 不好的做法
if (fd == -1) {
    fprintf(stderr, "错误: %s\n", strerror(errno));
}

线程安全注意事项

errno 在多线程环境中通常是线程安全的,每个线程都有自己的 errno 副本。但是,strerror() 函数不是线程安全的,应该使用 strerror_r() 或其他线程安全的替代方案。

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

void* thread_func(void* arg) {
    int thread_id = *(int*)arg;
    
    errno = thread_id;
    printf("线程 %d: errno = %d\n", thread_id, errno);
    
    char buf[256];
    strerror_r(errno, buf, sizeof(buf));
    printf("线程 %d: %s\n", thread_id, buf);
    
    return NULL;
}

int main() {
    pthread_t threads[3];
    int ids[3] = {1, 2, 3};
    
    for (int i = 0; i < 3; i++) {
        pthread_create(&threads[i], NULL, thread_func, &ids[i]);
    }
    
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }
    
    return 0;
}

总结

<errno.h> 提供的错误处理机制是 C 语言中处理系统调用和库函数错误的标准方式。正确使用 errno 可以帮助开发者:

  1. 识别错误类型 - 通过检查 errno 值确定具体的错误原因
  2. 提供有用的错误信息 - 使用 perror()strerror() 向用户显示错误信息
  3. 编写健壮的程序 - 通过适当的错误处理提高程序的可靠性

记住:

  • 只在函数调用失败时检查 errno
  • 在调用可能设置 errno 的函数前将其重置为 0
  • 使用有意义的错误信息帮助调试
  • 在多线程环境中注意线程安全性