Appearance
C 标准库 - <errno.h>
概述
<errno.h> 头文件定义了用于错误处理的宏和变量。当系统调用或库函数失败时,它们会设置一个整数错误代码,这个错误代码存储在 errno 变量中。
errno 变量
声明
c
int errno;errno 是一个全局变量,用于存储最近一次函数调用失败时的错误代码。
使用规则
- 只在函数调用失败时检查
errno - 在调用可能设置
errno的函数之前,应该将其设置为 0 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 directorystrerror()
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 可以帮助开发者:
- 识别错误类型 - 通过检查
errno值确定具体的错误原因 - 提供有用的错误信息 - 使用
perror()和strerror()向用户显示错误信息 - 编写健壮的程序 - 通过适当的错误处理提高程序的可靠性
记住:
- 只在函数调用失败时检查
errno - 在调用可能设置
errno的函数前将其重置为 0 - 使用有意义的错误信息帮助调试
- 在多线程环境中注意线程安全性