在 C 語言開發中,標準庫 I/O 函數(如 fopen、fread、fwrite 等)在執行失敗時,不會直接拋出異常,而是通過返回值指示錯誤,並將具體錯誤原因記錄在全域變數 errno 中。理解如何正確讀取並解釋 errno,是編寫健壯 C 程式的關鍵技能之一。本文將系統介紹 errno 的機制,以及 perror 和 strerror 兩大錯誤報告函數的用法與陷阱。
errno:錯誤碼的全域存儲errno?errno 是在 <errno.h> 中聲明的整型全域變數(實際上可能是宏,指向執行緒局部存儲)。當系統調用或標準庫函數發生錯誤時,它們會將對應的正整數錯誤碼賦值給 errno。每個錯誤碼都有唯一的符號常數名稱(如 ENOENT、EACCES、ENOMEM 等),具體定義請參考 man 3 errno。
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
// 此時 errno 已被設置,例如 ENOENT (No such file or directory)
// 處理錯誤...
}
注意: errno 只有在函數「明確表示失敗」(如返回 NULL、-1 或 EOF)時才具有意義。若函數成功返回,errno 的值未定義(可能保留舊值,也可能被清零),切勿以此判斷是否出錯。
perror:快速輸出錯誤描述#include <stdio.h>
void perror(const char *s);
perror 會向標準錯誤輸出(stderr)輸出一行訊息:
s 指向的字串(通常是程式上下文,如 "Open file data.txt");: 和空格;errno 的值,輸出系統定義的錯誤描述字串(如 No such file or directory);#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = fopen("abcde", "r");
if (fp == NULL) {
perror("Open file abcde"); // 輸出:Open file abcde: No such file or directory
exit(EXIT_FAILURE);
}
fclose(fp);
return 0;
}
errno 的易變性errno 是全域共享資源,幾乎所有系統調用和標準庫函數都可能修改它(極少數如 strcpy 除外)。因此,必須在發生錯誤的函數返回後「立即」讀取或打印 errno,中間不能插入任何其他函數調用。
// 錯誤示範
if (fp == NULL) {
perror("Open file"); // perror 內部可能調用其他函數,導致 errno 被覆蓋
printf("errno = %d\n", errno); // 此時 errno 可能已非 fopen 設置的值!
}
正確做法:若需同時打印描述與錯誤碼,先保存 errno:
if (fp == NULL) {
int saved_errno = errno; // 立即保存
perror("Open file");
fprintf(stderr, "errno = %d\n", saved_errno);
}
strerror:獲取錯誤描述字串#include <string.h>
char *strerror(int errnum);
errnum,返回指向靜態儲存區的錯誤描述字串指標。pthread_*)不使用 errno,而是直接返回錯誤碼,此時 perror 無法使用,必須用 strerror。#include <stdio.h>
#include <string.h>
#include <errno.h>
void log_error(const char *context) {
int err = errno; // 立即保存
fprintf(stderr, "[ERROR] %s: %s (errno=%d)\n",
context, strerror(err), err);
}
int main(void) {
FILE *fp = fopen("missing.txt", "r");
if (!fp) log_error("fopen missing.txt");
return 0;
}
errno。errno 存入區域變數。perror;需格式化、記錄或非 errno 來源錯誤碼用 strerror(或執行緒安全版 strerror_r)。perror 或自行格式化的字串應包含「做什麼操作、操作什麼資源」,便於定位問題。man 3 errno、man 3 perror、man 3 strerror<errno.h>、<string.h>、<stdio.h>