前言
忘記包含標頭檔導致隱式函數宣告,是 C 語言經典坑。本文記錄一次 `strerror` 未含 `` 導致 64 位元系統段錯誤的完整排查過程,並推廣至所有回傳指標的函數。
問題重現
#include
#include
// #include 故意省略
int main() {
char *perr = NULL;
errno = 14;
perr = strerror(errno); /* 隱式宣告:回傳 int */
puts(perr); /* 以 int 當指標 → 段錯誤 */
return 0;
}
編譯器警告 (關鍵線索)
warning: implicit declaration of function 'strerror'
warning: assignment makes pointer from integer without a cast

根因分析
- 隱式宣告規則:C89 允許未宣告函數,編譯器假設回傳 `int`、參數不定。
- 64 位元資料模型:指標 64 位元、`int` 32 位元。`strerror` 實際回傳 `char *` (64 位元),但呼叫端只取低 32 位元當作 `int`,再零擴展/符號擴展為 64 位元指標 → 位址截斷 → 非法記憶體存取。
- 連結階段:連結器在 `.so` 中找到真正 `strerror`,但呼叫約定已錯誤(呼叫端清理堆疊、暫存器傳遞參數均按 `int` 回傳處理),無法自動修正。

驗證實驗
- 加上 `#include ` → 正常編譯執行。
- `gcc -E` 預處理輸出確認無原型。
- `gcc -Wall` 必報警告。
推廣:所有回傳指標/大型結構的函數
| 函數 | 標頭檔 | 風險 |
| malloc/calloc/realloc | | 指標截斷 |
| fopen/fdopen | | FILE* 截斷 |
| strchr/strstr/strtok | | 指標截斷 |
| getenv/ctime/asctime | / | 指標截斷 |
最佳實踐
- 嚴格包含對應標頭檔,勿依賴隱式宣告。
- 啟用 `-Wall -Wextra -Werror=implicit-function-declaration`** 將警告升級為錯誤。
- 使用現代 C 標準 (C99/C11) 禁止隱式宣告。
- 定期檢視編譯警告,警告即錯誤。
附錄:x86-64 System V ABI 資料類型大小

- 指標、`long`、`size_t`:8 bytes
- `int`、`float`:4 bytes
- `short`:2 bytes
- `char`:1 byte
總結
一個缺失的 `#include `,透過隱式宣告→回傳類型錯誤→64 位元指標截斷→段錯誤,完美串聯起 C 編譯模型、ABI、連結器行為。這提醒我們:標頭檔非裝飾品,而是類型安全的契約。