文章目錄
- Linux 編程中的錯誤處理機制詳解 —— `errno` 全解析
- 一、什么是 `errno`?
- ?為什么需要 `errno`?
- ? 它在哪里定義?
- 二、errno 的設置與讀取規則
- ?? errno 不是總是有效!
- ?使用 errno 的正確步驟:
- 三、與 errno 配套使用的函數
- 示例:
- 四、errno 常見錯誤碼及含義
- 五、errno 是線程安全的嗎?
- 六、errno 封裝建議
- 七、errno 與系統調用的配合使用
- open() 示例:
- read() 示例:
- 八、如何查看 errno 對應的錯誤碼?
- 九、調試技巧:gdb 查看 errno
- 🔟 附錄:示例程序 - 模擬打開文件失敗
- ? 總結
Linux 編程中的錯誤處理機制詳解 —— errno
全解析
在 Linux C 系統編程中,我們時常會與各種系統調用打交道,如 open()
、read()
、write()
、fork()
、socket()
等。錯誤處理是程序魯棒性的重要組成部分,而 errno
就是標準庫為我們提供的重要錯誤處理機制。本文將全面詳細講解 errno
的原理、用法、注意事項、線程安全特性及實踐案例。
一、什么是 errno
?
errno
是一個全局變量,代表最近一次系統調用或標準庫函數失敗時的錯誤碼。它是 int
類型,但為了線程安全,現代實現中實際為“線程局部存儲”(TLS),即每個線程有自己的 errno 值。
?為什么需要 errno
?
很多系統調用或庫函數在失敗時只返回 -1 或 NULL,并不告訴你具體錯誤信息。此時就需要查詢 errno
來獲得錯誤原因。
? 它在哪里定義?
你需要引入頭文件:
#include <errno.h>
它在 <errno.h>
中的定義通常為:
extern int errno;
但實際實現如下(glibc):
#define errno (*__errno_location()) // 或 __errno()
意味著每個線程調用 errno
實際是訪問一個局部的 int
指針,避免線程間干擾。
二、errno 的設置與讀取規則
?? errno 不是總是有效!
- 僅當系統調用 失敗時,系統才會設置
errno
。 - 成功調用不會清除
errno
,所以你不能通過errno == 0
判斷成功與否。
?使用 errno 的正確步驟:
errno = 0; // 可選,清除舊錯誤
int fd = open("nofile.txt", O_RDONLY);
if (fd == -1) {// open 失敗,可以讀取 errnoprintf("Error code: %d\n", errno);printf("Message: %s\n", strerror(errno));
}
三、與 errno 配套使用的函數
函數 | 作用 |
---|---|
perror() | 打印系統調用錯誤及自定義前綴 |
strerror(errno) | 返回錯誤碼對應的錯誤字符串 |
strerror_r() | 線程安全版本,寫入用戶緩沖區 |
示例:
if ((fp = fopen("test.txt", "r")) == NULL) {perror("fopen");fprintf(stderr, "詳細錯誤:%s\n", strerror(errno));
}
四、errno 常見錯誤碼及含義
宏名 | 值 | 說明 |
---|---|---|
EPERM | 1 | 操作不被允許 |
ENOENT | 2 | 文件或目錄不存在 |
ESRCH | 3 | 沒有匹配的進程 |
EINTR | 4 | 系統調用中斷 |
EIO | 5 | I/O 錯誤 |
EBADF | 9 | 無效的文件描述符 |
EACCES | 13 | 權限不足 |
EEXIST | 17 | 文件已存在 |
ENOTDIR | 20 | 不是目錄 |
EISDIR | 21 | 是目錄 |
EINVAL | 22 | 參數無效 |
ENFILE | 23 | 系統打開的文件數已達上限 |
EMFILE | 24 | 進程打開的文件數已達上限 |
ENOSPC | 28 | 設備空間不足 |
可通過 man 3 errno
查看完整列表。
五、errno 是線程安全的嗎?
是的,在現代 glibc 實現中是線程安全的。
雖然 errno
看起來像一個全局變量,但實際是通過 __errno_location()
這樣的函數返回每個線程自己的 errno 變量,確保在多線程中使用不會沖突。
六、errno 封裝建議
為了代碼復用和統一日志格式,可以封裝錯誤打印:
void report_errno(const char* msg) {fprintf(stderr, "[ERROR] %s: %s (errno: %d)\n", msg, strerror(errno), errno);
}
使用示例:
if (close(fd) == -1) {report_errno("close failed");
}
七、errno 與系統調用的配合使用
open() 示例:
int fd = open("test.txt", O_RDONLY);
if (fd < 0) {perror("open");
}
read() 示例:
char buffer[128];
int n = read(fd, buffer, sizeof(buffer));
if (n < 0) {fprintf(stderr, "read error: %s\n", strerror(errno));
}
八、如何查看 errno 對應的錯誤碼?
方法一:終端查看 man 手冊
man 3 errno
man 3 strerror
方法二:查看頭文件定義
cat /usr/include/asm-generic/errno-base.h
cat /usr/include/asm-generic/errno.h
方法三:自定義測試代碼輸出:
for (int i = 0; i < 50; ++i) {printf("errno %d: %s\n", i, strerror(i));
}
九、調試技巧:gdb 查看 errno
在 GDB 中調試程序出錯時,可使用:
(gdb) print errno
(gdb) print strerror(errno)
確保你在出錯系統調用之后立即查看,否則 errno 值可能被覆蓋。
🔟 附錄:示例程序 - 模擬打開文件失敗
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>int main(void) {int fd = open("nonexist.txt", O_RDONLY);if (fd == -1) {fprintf(stderr, "打開失敗: %s (errno = %d)\n", strerror(errno), errno);return 1;}close(fd);return 0;
}
? 總結
關鍵點 | 說明 |
---|---|
errno 的本質 | 一個線程局部變量,記錄最近一次失敗的錯誤碼 |
設置時機 | 系統調用或函數失敗時設置,成功時不修改 |
獲取錯誤信息方式 | strerror(errno) 獲取描述,perror() 自動輸出 |
多線程安全性 | 是,現代系統通過線程局部存儲實現 |
使用注意事項 | 只能在調用失敗時使用,切勿誤判 |