C 標準庫函數失敗時,通常返回特殊值(如 NULL、-1、EOF),並將錯誤代碼存入全局變量 errno。errno 定義於 <errno.h>,為執行緒局部存儲(現代實現),各錯誤代碼對應具體原因(如 ENOENT、EACCES)。
#include <errno.h>
#include <stdio.h>
FILE* fp = fopen("data.txt", "r");
if (fp == NULL) {
// errno 現為具體錯誤碼,如 ENOENT=2
fprintf(stderr, "fopen failed, errno=%d\n", errno);
}
缺點:僅得數字,無語義,需查表對照。
#include <stdio.h>
FILE* fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("Open data.txt"); // 輸出:Open data.txt: No such file or directory
}
用戶字串: 系統錯誤信息errno 並轉換為文本。perror 內部可能調用其他庫函數,導致 errno 被覆寫。若需保留原始 errno,應先保存:int saved_errno = errno; perror(...);。#include <string.h>
#include <errno.h>
char* msg = strerror(errno); // 返回靜態緩衝區指標
fprintf(stderr, "Error: %s\n", msg);
strerror_s / strerror_r)。FILE* safe_fopen(const char* path, const char* mode) {
FILE* fp = fopen(path, mode);
if (fp == NULL) {
int err = errno; // 立即保存
fprintf(stderr, "ERROR [%s:%d] fopen('%s', '%s'): %s\n",
__FILE__, __LINE__, path, mode, strerror(err));
errno = err; // 還原(可選)
}
return fp;
}
| 陷阱 | 後果 | 對策 |
|---|---|---|
| 在檢查 errno 前調用其他庫函數 | errno 被覆寫,失去原始錯誤 | 緊跟失敗調用後檢查/保存 errno |
| 假設 perror 不修改 errno | 實際可能修改 | 先保存 errno 再調用 perror |
| 多線程直接用 strerror 返回指標 | 靜態緩衝區競爭 | 用 strerror_r 或複製字串 |
| 忽略返回值直接用 errno | 函數成功時 errno 未定義 | 僅在返回值指示失敗時檢查 errno |
EACCES (13) — 權限被拒EAGAIN (11) — 資源暫不可用(非阻塞 I/O)EBADF (9) — 非法文件描述符EINTR (4) — 系統調用被信號中斷EINVAL (22) — 參數無效EMFILE (24) — 進程打開文件過多ENFILE (23) — 系統打開文件過多ENOENT (2) — 文件/目錄不存在ENOMEM (12) — 記憶體不足ENOSPC (28) — 磁盤空間不足strerror_r 或 C11 strerror_s。熟練掌握此三件套,讓你的 C 程式在面對 I/O 失敗時既健壯又可診斷。