strerror
和 errno
详解
cpp
printf("Error: %s\n", strerror(errno));
这行代码用于在 C 语言中输出系统错误信息,但它与 Windows 的 GetLastError()
有重要区别。下面我将详细解释每个部分及其工作原理。
1. 组件解析
errno
- 定义 :
errno
是一个全局整型变量(在 C11 标准中改为线程局部存储),定义在<errno.h>
头文件中 - 作用:存储标准 C 库函数执行失败时的错误代码
- 特点 :
- 由标准 C 库函数设置(如文件操作、内存分配等)
- 不是 Windows API 错误(Windows API 使用
GetLastError()
) - 在多线程环境中,现代编译器通常将其实现为线程安全的
strerror
- 原型 :
char *strerror(int errnum);
- 作用:将错误代码转换为可读的错误描述字符串
- 参数 :接收一个错误码(通常是
errno
的值) - 返回值:指向静态分配的字符串的指针,描述错误信息
2. 典型使用场景
c
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
printf("Error: %s\n", strerror(errno));
// 或使用 perror 更简单的方式:
perror("Failed to open file");
}
return 0;
}
3. 与 Windows API GetLastError()
的区别
特性 | errno/strerror | GetLastError()/FormatMessage |
---|---|---|
来源 | 标准 C 库函数设置 | Windows API 函数设置 |
头文件 | <errno.h>, <string.h> | <windows.h> |
错误码范围 | 标准 C 定义的错误码 | Windows 系统错误码 |
线程安全 | 现代实现中是线程安全的 | 本身就是线程特定的 |
典型错误示例 | ENOENT(2): 文件不存在 | ERROR_FILE_NOT_FOUND(2): 系统找不到文件 |
获取字符串的函数 | strerror() | FormatMessage() |
4. 常见问题
问题1:为什么我的 Windows API 错误用 strerror 显示不正确?
-
原因 :Windows API 错误应该使用
GetLastError()
+FormatMessage()
-
示例 :
c// 错误方式(不适用于Windows API错误) CloseHandle(invalid_handle); printf("%s\n", strerror(errno)); // 不会显示正确的Windows错误 // 正确方式 CloseHandle(invalid_handle); printf("Error: %d\n", GetLastError()); // 使用FormatMessage更好
问题2:errno 的值何时会被覆盖?
- 规则 :
- 成功的库函数调用不会修改
errno
(标准规定) - 某些实现可能在成功调用时将
errno
设为 0 - 应该立即在函数调用失败后检查
errno
- 成功的库函数调用不会修改
问题3:strerror 的返回值有效期是多久?
- 答案 :
- 返回指向静态字符串的指针,不需要释放
- 但后续调用可能会覆盖该内容(非线程安全实现中)
- 在 C11 中可以使用
strerror_s
更安全的版本
5. 替代方案
(1) perror() - 更简单的打印方式
c
fopen("nonexistent.txt", "r");
if (errno) {
perror("File open failed");
// 输出: "File open failed: No such file or directory"
}
(2) strerror_r (线程安全版本, POSIX)
c
char buf[256];
strerror_r(errno, buf, sizeof(buf));
printf("Error: %s\n", buf);
(3) C11 的 strerror_s
c
char buf[256];
strerror_s(buf, sizeof(buf), errno);
printf("Error: %s\n", buf);
6. 典型 errno 错误码示例
错误码 | 值 | 描述 |
---|---|---|
EPERM | 1 | 操作不允许 |
ENOENT | 2 | 文件或目录不存在 |
EIO | 5 | 输入/输出错误 |
EBADF | 9 | 错误的文件描述符 |
EAGAIN | 11 | 资源暂时不可用,请重试 |
EACCESS | 13 | 权限不足 |
EFAULT | 14 | 错误的地址 |
EBUSY | 16 | 设备或资源忙 |
EEXIST | 17 | 文件已存在 |
EINVAL | 22 | 无效参数 |
ENOSPC | 28 | 设备无剩余空间 |
7. 最佳实践建议
-
在 Windows 编程中:
- 对于 Win32 API 错误,始终使用
GetLastError()
+FormatMessage()
- 对于标准 C 库函数,可以使用
errno
+strerror()
- 对于 Win32 API 错误,始终使用
-
跨平台编程:
c#ifdef _WIN32 // Windows 错误处理 #else // POSIX 错误处理 #endif
-
错误处理模板:
c#include <stdio.h> #include <errno.h> #include <string.h> void print_error(const char *context) { #ifdef _WIN32 DWORD error = GetLastError(); LPSTR buffer = NULL; FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, error, 0, (LPSTR)&buffer, 0, NULL); fprintf(stderr, "%s: %s (code %d)\n", context, buffer, error); LocalFree(buffer); #else fprintf(stderr, "%s: %s (code %d)\n", context, strerror(errno), errno); #endif }
理解这些区别对于正确诊断和报告系统错误至关重要,特别是在混合使用标准库函数和平台特定API时。