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

相关推荐
.YM.Z1 小时前
C语言——操作符
c语言·开发语言·算法
柒柒的代码学习日记1 小时前
qsort函数
c语言
秋山落叶万岭花开ღ4 小时前
C语言自定义类型:结构体详解
c语言
浩皓素4 小时前
探秘C语言中的递归:从基础概念到实战应用
c语言
2301_817031654 小时前
C语言--字符函数
c语言·开发语言
jz_ddk6 小时前
[学习]RTKLib详解:ppp.c与ppp_ar.c
c语言·学习·ar
黄皮の电气鼠6 小时前
链表——C语言
c语言·数据结构·链表
Susea&6 小时前
趣味编程:梦幻万花筒
c语言·算法·技术美术
一支闲人6 小时前
C语言初阶:数组
c语言·c语言基础知识·适用于新手小白