C 語言文件操作分為 **文本流** 與 **二進制流** 兩種模式。文本流適合人類可讀配置/日志,二進制流適合高性能數據持久化、結構體序列化、跨平台數據交換。本文深度對比兩模式差異,重點講解 `fread`/`fwrite` 二進制讀寫核心 API、內存佈局映射原理、常見坑點與最佳實踐。
| 特性 | 文本模式 (`"r"`, `"w"`, `"a"`) | 二進制模式 (`"rb"`, `"wb"`, `"ab"`) |
| 數據表示 | 字符序列 (ASCII/UTF-8 編碼) | 原始位模式 (內存位映像) |
| 換行符處理 | **平台相關轉換**:Windows `\r\n` ⇄ `\n`,Linux/Unix 無轉換 | **無任何轉換**,原樣讀寫 |
| EOF 標記 | Windows 可能識別 `Ctrl+Z` (0x1A) 為 EOF | 純粹按字節計數/文件末尾 |
| 適用場景 | 配置文件、日志、CSV、源代碼、人類閱讀 | 圖像/音視頻、結構體數組、數據庫頁、網絡包存檔 |
| 可移植性 | 跨平台文本文件需注意換行符 | **完全可移植** (同端序前提下) |
二、二進制讀寫核心 API:`fwrite` / `fread`
size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream);
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
參數解析:
- `buffer`:數據緩衝區地址 (任意類型指針 `void*`)
- `size`:**單個元素大小** (字節),通常用 `sizeof(Type)`
- `count`:元素個數
- `stream`:`fopen` 返回的 `FILE*` (必須帶 `b` 模式)
- **返回值**:**成功讀寫的元素個數** (非字節數)。返回值 `< count` 表示錯誤或 EOF。
經典用法:結構體數組整體寫入/讀取
#include
#include
typedef struct {
int id;
char name[32];
float score;
} Student;
int main() {
Student class[3] = {
{1, "Alice", 92.5f},
{2, "Bob", 87.0f},
{3, "Charlie", 95.5f}
};
FILE *fp = fopen("students.dat", "wb");
if (!fp) return 1;
// 寫入:一次性寫入 3 個 Student 結構體
size_t written = fwrite(class, sizeof(Student), 3, fp);
printf("Written %zu records\n", written); // 3
fclose(fp);
// 讀取:還原到新數組
Student read_class[3];
fp = fopen("students.dat", "rb");
size_t read = fread(read_class, sizeof(Student), 3, fp);
printf("Read %zu records\n", read); // 3
for (int i = 0; i < read; i++)
printf("%d: %s %.1f\n", read_class[i].id, read_class[i].name, read_class[i].score);
fclose(fp);
return 0;
}
三、內存佈局可視化:為什麼二進制「快」且「隱私」
假設 `int a = 64` (佔 2 字節,小端序):
| 存儲方式 | 內存/文件位模式 (十六進制) | 文本編輯器顯示 | 說明 |
| 二進制 `fwrite(&a, 2, 1, fp)` | `40 00` (小端: 低位在前) | 亂碼:`@` + NUL (0x40='@', 0x00=NUL) | 直接寫入內存位模式,非文本 |
| 文本 `fprintf(fp, "%d", a)` | `36 34` ('6'=0x36, '4'=0x34) | 可讀:`64` | 轉換為字符編碼,佔用更多空間 |
核心結論:
- **二進制 = 內存位映像直接落盤**,CPU 免去「數值 ⇄ 字符」解析開銷,**速度快、體積小、精度無損**。
- **非加密**:二進制僅是「不直接可讀」,`char` 字符串字段仍可被文本編輯器識別。**真正加密需 AES/ChaCha20 等算法**。
- **端序陷阱**:跨平台讀寫時,需約定網絡序 (大端) 或統一小端,或在文件頭寫入 Magic Number 標識端序。
四、進階實戰技巧
1. 結構體對齊與 `#pragma pack`
// 默認對齊可能導致結構體大小跨平台不一致
#pragma pack(push, 1) // 1 字節對齊,消除填充
typedef struct {
char type; // 1
int len; // 4
short checksum; // 2
} PacketHeader; // 大小 = 7 字節 (無填充)
#pragma pack(pop)
// 寫入時確保雙方協議一致,或顯式序列化各字段 (網絡序 htonl/ntohl)
2. 大文件分塊讀寫 (避免大緩衝區)
#define CHUNK_SIZE (64 * 1024)
char buf[CHUNK_SIZE];
while ((n = fread(buf, 1, CHUNK_SIZE, src)) > 0)
fwrite(buf, 1, n, dst);
3. 錯誤處理模板
if (fwrite(data, sizeof(Item), count, fp) != count) {
if (ferror(fp)) perror("Write error");
else if (feof(fp)) fprintf(stderr, "Unexpected EOF\n");
// 處理錯誤
}
4. 隨機訪問:`fseek` / `ftell` / `fgetpos` / `fsetpos`
// 讀取第 i 個記錄
fseek(fp, i * sizeof(Record), SEEK_SET);
fread(&rec, sizeof(Record), 1, fp);
// 獲取文件大小
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
五、文本 vs 二進制選擇決策表
| 需求 | 推薦模式 | 關鍵 API |
| 人類需閱讀/編輯配置 | 文本 | `fprintf`/`fscanf`/`fgets`/`fputs` |
| 高性能海量數據存儲 | 二進制 | `fwrite`/`fread` + 結構體 |
| 跨平台數據交換 | 二進制 + 協議規範 | `fwrite`/`fread` + `htonl`/`ntohl` |
| 日志/文本處理管道 | 文本 | `fprintf`/`fgets` |
| 嵌入式固件/配置區 | 二進制 | `fwrite`/`fread` + CRC 校驗 |
六、總結
- 二進制模式 (`"rb"/"wb"`) 是高性能、精確、可移植數據持久化的基石。
- `fwrite`/`fread` 以「元素大小 × 個數」為單位操作內存塊,**返回元素數而非字節數**,檢查返回值是必修課。
- **結構體直接讀寫** 便利但隱含對齊、端序、版本兼容風險,**嚴肅項目建議顯式序列化 (Protobuf/FlatBuffers/手寫)**。
- 二進制文件**不是加密**,敏感數據必須應用層加密。
- 文本模式處理換行轉換,二進制模式**絕不轉換**,跨平台文本文件建議統一 LF (`\n`)。
- 上一篇文章: C語言賦值與自增是原子操作嗎?從汇編、CPU流水線、總線鎖定深度解析
- 下一篇文章: 字符集核心技術:Unicode、UTF-8/16、多字節字符集、Windows API 與 C/C++ 字符串類完全指南
Copyright ©
程式碼之家 All Rights Reserved