C 標準 I/O 庫(stdio.h)提供跨平台、緩衝、格式化的檔案操作介面。深入理解其**三種緩衝模式**、**開檔模式語意**、**二進制讀寫限制**,能有效避免資料遺失、效能低落、跨平台亂碼等棘手問題。本文結合 APUE 經典內容與實戰經驗總結。
| 模式 | 觸發條件 | 典型場景 | 注意事項 |
|---|---|---|---|
| 全緩衝 | 緩衝區滿 / fflush() / 正常程式結束 | 磁碟檔案(預設) | 大量資料高效;異常終止可能丟失未刷新資料 |
| 行緩衝 | 遇到 \n / 緩衝區滿 / 讀取無緩衝流 / fflush() | 標準輸入/輸出(終端互動) | 確保提示訊息即時顯示;重導向至檔案時變全緩衝 |
| 無緩衝 | 每次 I/O 直接系統呼叫 | 標準錯誤 stderr | 錯誤資訊即時輸出;效能最低 |
控制函數: setvbuf()、setbuf() 可在 fopen 後、首次 I/O 前切換模式與緩衝區大小。
FILE *fopen(const char *filename, const char *mode);
| mode | 讀 | 寫 | 建立 | 截斷 | 定位 | 備註 |
|---|---|---|---|---|---|---|
| r | ✓ | 開頭 | 檔案必須存在 | |||
| w | ✓ | ✓ | ✓ | 開頭 | 清空現有內容 | |
| a | ✓ | ✓ | 結尾 | 追加寫入 | ||
| r+ | ✓ | ✓ | 開頭 | 檔案必須存在 | ||
| w+ | ✓ | ✓ | ✓ | ✓ | 開頭 | 清空現有內容 |
| a+ | ✓ | ✓ | ✓ | 結尾 | 讀可定位,寫永遠追加 |
二進制模式: 在 mode 字串中加入 b(如 "rb"、"wb+")。Windows 上區分換行符轉換(\n ⇄ \r\n),Linux/Unix 忽略 b。**跨平台處理二進制檔案務必加 b**。
**當以讀寫模式開啟檔案(mode 含
+)時,輸出操作與輸入操作之間必須插入:fflush()、fseek()、fsetpos()、rewind()其中之一。**
否則行為未定義(資料損壞、讀到舊資料、檔案位置錯亂)。
fclose(fp):刷新輸出緩衝、丟棄輸入緩衝、釋放結構體、釋放檔案描述符。return 0/exit()):所有流自動刷新並關閉。_exit()/abort()/信號終止):**不保證**刷新緩衝,關鍵資料須手動 fflush() 或用無緩衝流。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
| 現象 | 原因 | 對策 |
|---|---|---|
printf 不顯示提示,scanf 卡住 | stdout 行緩衝,無 \n 未刷新 | fflush(stdout) 或 printf("...\\n") |
| 重導向後輸出順序錯亂 | stdout 變全緩衝,stderr 仍無緩衝 | 關鍵日誌用 stderr 或手動 fflush |
| 二進制讀寫結構體跨平台錯誤 | 記憶體對齊、大小端、填充位元組差異 | 改用序列化庫 |
fgets 讀取最後一行無換行符 | 檔案結尾無 \n | 檢查回傳值非 NULL 即有效 |
追加模式 fseek 失效 | a+ 模式寫入永遠在結尾 | 改用 r+ 並手動 fseek 到末尾 |
<fstream>(RAII)、<filesystem> (C++17)、fmt 庫(格式化)。mmap、io_uring (Linux)、記憶體映射檔案。boost::iostreams、libuv 非同步 I/O。C 標準 I/O 以「緩衝 + 格式化 + 跨平台」三大特性橫跨四十年仍是系統程式基石。掌握**緩衝模式選擇**、**讀寫切換屏障**、**二進制 I/O 侷限性**,並養成「關鍵點 fflush、錯誤走 stderr、持久化用序列化」的工程習慣,即能駕馭絕大多數檔案操作場景。