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

相关推荐
Aurorar0rua2 分钟前
CS50 x 2024 Notes C - 07
c语言·学习方法
爱编码的小八嘎3 分钟前
C语言完美演绎9-15
c语言
weixin_421725261 小时前
C语言常用字符串函数:长度、比较、拼接和查找
c语言·字符串函数·查找·比较·长度
yzq1991272 小时前
C语言#和##的用法(附带示例)
c语言·宏定义·预处理运算符·字符串化·标记连接
无敌昊哥战神3 小时前
【LeetCode 37】解数独 (Sudoku Solver) —— 回溯法详解 (Python/C/C++)
c语言·c++·python·算法·leetcode
浩浩测试一下4 小时前
栈帧 抬栈与平栈 (逆向分析)
汇编·windows api·堆栈·windows编程·windows 开发
jinyishu_4 小时前
链表经典OJ题
c语言·数据结构·算法·链表
爱编码的小八嘎4 小时前
C语言完美演绎9-14
c语言
li1670902705 小时前
第二十五章:C++11(下)
c语言·开发语言·数据结构·c++
代码中介商16 小时前
银行管理系统的业务血肉 —— 流程、状态机、输入校验与持久化(下篇)
c语言·算法