strerror
和 errno
詳解
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. 典型使用場景
#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()
- 示例:
// 錯誤方式(不適用于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() - 更簡單的打印方式
fopen("nonexistent.txt", "r");
if (errno) {perror("File open failed"); // 輸出: "File open failed: No such file or directory"
}
(2) strerror_r (線程安全版本, POSIX)
char buf[256];
strerror_r(errno, buf, sizeof(buf));
printf("Error: %s\n", buf);
(3) C11 的 strerror_s
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 錯誤,始終使用
-
跨平臺編程:
#ifdef _WIN32 // Windows 錯誤處理 #else // POSIX 錯誤處理 #endif
-
錯誤處理模板:
#include <stdio.h> #include <errno.h> #include <string.h>void print_error(const char *context) {#ifdef _WIN32DWORD 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);#elsefprintf(stderr, "%s: %s (code %d)\n", context, strerror(errno), errno);#endif }
理解這些區別對于正確診斷和報告系統錯誤至關重要,特別是在混合使用標準庫函數和平臺特定API時。