【C/C++】errno/strerror 和 GetLastError()/FormatMessage 的区别

strerrorerrno 详解

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 的值何时会被覆盖?

  • 规则
    1. 成功的库函数调用不会修改 errno(标准规定)
    2. 某些实现可能在成功调用时将 errno 设为 0
    3. 应该立即在函数调用失败后检查 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. 最佳实践建议

  1. 在 Windows 编程中

    • 对于 Win32 API 错误,始终使用 GetLastError() + FormatMessage()
    • 对于标准 C 库函数,可以使用 errno + strerror()
  2. 跨平台编程

    c 复制代码
    #ifdef _WIN32
    // Windows 错误处理
    #else
    // POSIX 错误处理
    #endif
  3. 错误处理模板

    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时。

相关推荐
麻瓜老宋4 小时前
AI开发C语言应用按步走,表达式计算器calc的第二十二步,分号赋值链式修复、TOKEN_ASSIGN
c语言·开发语言·atomcode
SilentSlot9 小时前
【C/C++】手写 DPDK 协议栈(十一):基于 PPS、SYN 比例和源 IP 熵的 DDoS 检测
c语言·c++·tcp/ip
冻柠檬飞冰走茶11 小时前
PTA基础编程题目集 7-15 计算圆周率(C语言实现)
c语言·开发语言·数据结构·算法
Rabitebla13 小时前
C++ 内存管理全面复习:从内存分布到 operator new/delete
java·c语言·开发语言·c++·算法·leetcode
SilentSlot13 小时前
【C/C++】手写 DPDK 协议栈(八):用五元组 Hash 加速连接定位
c语言·c++·哈希算法
Tisfy14 小时前
LeetCode 3517.最小回文排列 I:排序(Python两行版) / 计数(O(n)时间+O(C)空间+字符串原地修改)
c语言·python·leetcode·字符串·排序·计数排序·回文
菜鸟的升级路14 小时前
C语言栈刷题:LeetCode 20 有效的括号完整解题笔记
c语言·笔记·leetcode
冻柠檬飞冰走茶14 小时前
PTA基础编程题目集 7-13 日K蜡烛图(C语言实现)
c语言·开发语言·数据结构·算法
SilentSlot16 小时前
【C/C++】手写 DPDK 协议栈(十二):TCP 并发与自实现 epoll 的就绪事件分发
c语言·c++·tcp/ip
SilentSlot16 小时前
【C/C++】手写 DPDK 协议栈(六):TCP 三次握手、状态机与数据回显
c语言·c++·tcp/ip