首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言二進制文件讀寫完全指南:fopen/fread/fwrite/fseek 核心函數與文件拷貝實戰

     編輯:關於C語言

前言

二進制文件操作是系統編程、嵌入式開發、數據持久化的基石。本文系統講解 fopen、fread、fwrite、fseek 四大核心函數的**標準簽名、參數語義、返回值、錯誤處理、模式字符串**;並給出**高性能文件拷貝**完整實現,解析關鍵優化點。

一、核心函數詳解

1. 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

關鍵點:

  • Windows 必加 b:否則 \n ↔ \r\n 轉換破壞二進制數據。Linux/Unix 忽略 b 但建議統一加上保證可移植。
  • 返回值檢查:if (!fp) { perror("fopen"); return -1; }。
  • 權限:創建文件默認 0666 & ~umask。

2. 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) (按結構體讀)。

3. fwrite:向流寫入數據塊


size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 參數/返回值語義與 fread 對稱。返回寫入的單元個數,< nmemb 表示寫入錯誤 (磁盤滿、權限等)。
  • 緩衝區刷新:fflush(fp) 強制寫入磁盤,fclose 自動刷新。

4. 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。

二、高性能文件拷貝實戰 (標準 C 實現)

2.1 完整代碼


#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;
}

2.2 關鍵優化點解析

優化點 說明
大塊緩衝區 1MB (或 64KB-4MB),減少系統調用次數,利用磁盤順序讀寫帶寬。
按字節單元讀寫 fread(buf, 1, BUF_SIZE, ...) 返回值直接為**字節數**,邏輯簡潔,無需乘除。
讀多少寫多少 fwrite(buf, 1, n, dst) 精確寫入實際讀取字節數,處理末尾不滿緩衝區情況。
錯誤檢查完備 檢查 fopen/malloc/fwrite 返回值,ferror 區分讀錯誤與 EOF。
資源釋放路徑全覆蓋 所有錯誤分支均 free/fclose,無洩漏。

2.3 進階優化方向

  1. 內存對齊:posix_memalign(&buf, 4096, BUF_SIZE) 4KB 對齊,可配合 O_DIRECT 繞過頁面緩存 (需 open 而非 fopen)。
  2. 異步 I/O:aio_read/aio_write (POSIX) 或 ReadFileEx (Windows),CPU 與 I/O 並行。
  3. mmap 零拷貝:mmap 源文件 + write 目標文件,內核直接頁面拷貝,用戶態零數據搬移。
  4. sendfile / splice (Linux):內核態文件描述符間傳輸,極致性能。

三、常見陷阱與避坑指南

  1. ❌ 文本模式 ("r"/"w") 讀寫二進制 → Windows 換行符破壞數據。✅ **始終用 b 模式**。
  2. ❌ fread(buf, sizeof(Struct), N, fp) 結構體含填充字節/指針 → 跨平台/跨版本不可移植。✅ **序列化為扁平字節流** (Protocol Buffers/FlatBuffers/手工編碼)。
  3. ❌ 忽略 fread 返回值 < nmemb 直接處理緩衝區 → 處理垃圾數據。✅ **嚴格用返回值 n 作為有效長度**。
  4. ❌ fseek(fp, -10, SEEK_CUR) 後直接 fread 未檢查返回值 → 定位失敗未發現。✅ **檢查 fseek 返回值 (0 成功)**。
  5. ❌ 32 位系統 fseek/ftell 處理 >2GB 文件溢出。✅ **用 fseeko/ftello 或 64 位專用 API**。

四、總結

二進制文件操作的核心公式:**fopen(..., "rb"/"wb") + 大緩衝區 + fread/fwrite 返回值驅動循環 + 完備錯誤處理**。掌握 fseek 隨機定位、ftell 獲取大小、ferror/feof 區分異常,即可構建健壯的文件存儲層。進階場景請轉向 mmap/sendfile/io_uring 等零拷貝/異步技術,但標準 C stdio.h 的可移植性與穩定性始終是基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved