C 語言不提供例外處理,而是採用「回傳值 + 全域錯誤碼」的極簡機制。這設計雖然原始,卻賦予系統程式設計師對錯誤流程的完全掌控。本文系統梳理 errno、perror()、strerror() 的協作關係,並建立工業級錯誤處理最佳實踐。
| 元件 | 型別/原型 | 角色 | 關鍵特性 |
|---|---|---|---|
errno | int (巨集展開為 thread-local 變數) | 全域錯誤碼存儲 | 標準庫函式失敗時設定;成功不會清零 |
perror() | void perror(const char *msg) | 輸出錯誤訊息至 stderr | 格式:msg: errno對應字串;自動換行 |
strerror() | char *strerror(int errnum) | 取得錯誤碼對應字串 | 回傳靜態緩衝區指標;非執行緒安全 (C11 用 strerror_s) |
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
FILE *open_config(const char *path) {
FILE *fp = fopen(path, "r");
if (fp == NULL) {
int err = errno; // 1. 立即保存 errno
fprintf(stderr, "[ERROR] 開啟 '%s' 失敗: ", path);
perror(""); // 2. 輸出系統錯誤訊息
// 或自訂格式:
// fprintf(stderr, "錯誤碼 %d: %s\n", err, strerror(err));
errno = 0; // 3. 可選:重置 errno
return NULL;
}
return fp;
}
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
FILE *fp = fopen("unexist.txt", "rb");
if (fp == NULL) {
int errnum = errno; // 關鍵:先保存,後續呼叫可能覆蓋 errno
fprintf(stderr, "錯誤號: %d\n", errnum);
perror("通過 perror 輸出錯誤");
fprintf(stderr, "打開文件錯誤: %s\n", strerror(errnum));
return EXIT_FAILURE;
}
fclose(fp);
return EXIT_SUCCESS;
}
輸出範例:
錯誤號: 2 通過 perror 輸出錯誤: No such file or directory 打開文件錯誤: No such file or directory
整數除零在 C 標準屬於未定義行為(UB),不會設定 errno,程式直接崩潰(SIGFPE)。必須事前檢查:
#include <stdio.h>
#include <stdlib.h>
int safe_divide(int dividend, int divisor, int *result) {
if (divisor == 0) {
fprintf(stderr, "除數為 0,運算中止\n");
return -1; // 自訂錯誤碼
}
*result = dividend / divisor;
return 0;
}
int main() {
int q;
if (safe_divide(20, 0, &q) != 0) {
return EXIT_FAILURE;
}
printf("商 = %d\n", q);
return EXIT_SUCCESS;
}
<stdlib.h> 定義兩個標準退出碼:
EXIT_SUCCESS (0):正常結束EXIT_FAILURE (1):異常結束搭配 exit() 可在深層巢狀函式中直接終止程式:
if (critical_error) {
fprintf(stderr, "致命錯誤: %s\n", strerror(errno));
exit(EXIT_FAILURE); // 立即終止,執行 atexit 註冊的清理函式
}
| 問題 | C99/C11 前 | C11 解決方案 |
|---|---|---|
errno 多執行緒競爭 | 全域變數,需鎖保護 | 編譯器實作為 thread-local storage,各執行緒獨立 |
strerror 回傳靜態緩衝區 | 多執行緒呼叫會覆蓋 | strerror_s(char *buf, size_t buflen, int errnum) 呼叫端提供緩衝區 |
perror 非原子輸出 | 多執行緒輸出交錯 | 使用 flockfile(stderr) / funlockfile(stderr) 鎖定串流 |
errno = 0; 在 main 開頭執行。errno 到區域變數。stdout,確保管線重導向不受干擾。errno,業務邏輯錯誤自定義 enum ErrorCode,避免語意混淆。free()、fclose()、pthread_mutex_unlock(),善用 goto cleanup 模式集中釋放。DEBUG/INFO/WARN/ERROR/FATAL,生產環境關閉 DEBUG 輸出。C 語言錯誤處理雖無語法糖,但「回傳值 + errno」機制極度輕量、可預測、零開銷。掌握 errno 保存時機、善用 perror/strerror 產出人類可讀訊息、建立分層錯誤碼體系,是編寫健壯系統軟體(OS 核心、資料庫、嵌入式固件)的核心基本功。記住:錯誤處理不是事後補救,而是 API 設計的一部分。