二進制文件操作是系統編程、嵌入式開發、數據持久化的基石。本文系統講解 fopen、fread、fwrite、fseek 四大核心函數的**標準簽名、參數語義、返回值、錯誤處理、模式字符串**;並給出**高性能文件拷貝**完整實現,解析關鍵優化點。
fopen:打開文件流
FILE *fopen(const char *path, const char *mode);
| 模式 | 含義 | 文件存在 | 文件不存在 | 二進制關鍵 |
|---|---|---|---|---|
rb |
只讀 | 打開 | 失敗 (NULL) | ✅ 必加 b |
wb |
只寫 (截斷/創建) | 清空內容 | 創建 | ✅ 必加 b |
ab |
追加寫入 | 定位到末尾 | 創建 | ✅ 必加 b |
r+b / rb+ |
讀寫 (不截斷) | 打開 | 失敗 | ✅ 必加 b |
w+b / wb+ |
讀寫 (截斷/創建) | 清空 | 創建 | ✅ 必加 b |
a+b / ab+ |
讀寫 (追加) | 定位到末尾 | 創建 | ✅ 必加 b |
關鍵點:
b:否則 \n ↔ \r\n 轉換破壞二進制數據。Linux/Unix 忽略 b 但建議統一加上保證可移植。if (!fp) { perror("fopen"); return -1; }。fread:從流讀取數據塊
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr 目標緩衝區,size 單元大小,nmemb 單元個數,stream 流指針。nmemb 表示 EOF 或錯誤,需配合 feof()/ferror() 區分。fread(buf, 1, sizeof(buf), fp) (按字節讀) 或 fread(arr, sizeof(Struct), N, fp) (按結構體讀)。fwrite:向流寫入數據塊
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
fread 對稱。返回寫入的單元個數,< nmemb 表示寫入錯誤 (磁盤滿、權限等)。fflush(fp) 強制寫入磁盤,fclose 自動刷新。fseek / ftell / rewind:隨機訪問定位
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream); // 等價 fseek(stream, 0, SEEK_SET) + 清錯誤標誌
whence 常量 |
值 | 含義 | 示例 |
|---|---|---|---|
SEEK_SET |
0 | 文件開頭 | fseek(fp, 100, SEEK_SET) → 第 100 字節 |
SEEK_CUR |
1 | 當前位置 | fseek(fp, -10, SEEK_CUR) → 向前 10 字節 |
SEEK_END |
2 | 文件末尾 | fseek(fp, 0, SEEK_END) → 末尾 (配合 ftell 獲取大小) |
注意:offset 為 long,32 位系統限制 2GB。大文件需用 fseeko/ftello (POSIX, off_t) 或 Windows _fseeki64。
#include
#include
#include
#define BUF_SIZE (1024 * 1024) // 1MB 緩衝區
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s \n", argv[0]);
return 1;
}
FILE *src = fopen(argv[1], "rb");
if (!src) { perror("fopen src"); return 1; }
FILE *dst = fopen(argv[2], "wb");
if (!dst) { perror("fopen dst"); fclose(src); return 1; }
char *buf = malloc(BUF_SIZE);
if (!buf) { fprintf(stderr, "malloc failed\n"); fclose(src); fclose(dst); return 1; }
size_t n;
while ((n = fread(buf, 1, BUF_SIZE, src)) > 0) {
size_t written = fwrite(buf, 1, n, dst);
if (written != n) {
fprintf(stderr, "fwrite error: %zu/%zu\n", written, n);
free(buf);
fclose(src);
fclose(dst);
return 1;
}
}
if (ferror(src)) {
perror("fread error");
free(buf);
fclose(src);
fclose(dst);
return 1;
}
free(buf);
fclose(src);
fclose(dst);
return 0;
}
| 優化點 | 說明 |
|---|---|
| 大塊緩衝區 | 1MB (或 64KB-4MB),減少系統調用次數,利用磁盤順序讀寫帶寬。 |
| 按字節單元讀寫 | fread(buf, 1, BUF_SIZE, ...) 返回值直接為**字節數**,邏輯簡潔,無需乘除。 |
| 讀多少寫多少 | fwrite(buf, 1, n, dst) 精確寫入實際讀取字節數,處理末尾不滿緩衝區情況。 |
| 錯誤檢查完備 | 檢查 fopen/malloc/fwrite 返回值,ferror 區分讀錯誤與 EOF。 |
| 資源釋放路徑全覆蓋 | 所有錯誤分支均 free/fclose,無洩漏。 |
posix_memalign(&buf, 4096, BUF_SIZE) 4KB 對齊,可配合 O_DIRECT 繞過頁面緩存 (需 open 而非 fopen)。aio_read/aio_write (POSIX) 或 ReadFileEx (Windows),CPU 與 I/O 並行。mmap 源文件 + write 目標文件,內核直接頁面拷貝,用戶態零數據搬移。"r"/"w") 讀寫二進制 → Windows 換行符破壞數據。✅ **始終用 b 模式**。fread(buf, sizeof(Struct), N, fp) 結構體含填充字節/指針 → 跨平台/跨版本不可移植。✅ **序列化為扁平字節流** (Protocol Buffers/FlatBuffers/手工編碼)。fread 返回值 < nmemb 直接處理緩衝區 → 處理垃圾數據。✅ **嚴格用返回值 n 作為有效長度**。fseek(fp, -10, SEEK_CUR) 後直接 fread 未檢查返回值 → 定位失敗未發現。✅ **檢查 fseek 返回值 (0 成功)**。fseek/ftell 處理 >2GB 文件溢出。✅ **用 fseeko/ftello 或 64 位專用 API**。二進制文件操作的核心公式:**fopen(..., "rb"/"wb") + 大緩衝區 + fread/fwrite 返回值驅動循環 + 完備錯誤處理**。掌握 fseek 隨機定位、ftell 獲取大小、ferror/feof 區分異常,即可構建健壯的文件存儲層。進階場景請轉向 mmap/sendfile/io_uring 等零拷貝/異步技術,但標準 C stdio.h 的可移植性與穩定性始終是基石。