首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

C 標準庫 fread 後直接 fwrite 失敗:需先 fseek 切換模式

     編輯:程式設計疑難解答

問題現象

程式碼開啟 rb+ 模式,fread 讀取 20 位元組後,直接 fwrite 寫入失敗 (回傳 0),errno 為 0。

根本原因

C 標準 (C11 §7.21.5.3) 規定:更新模式 (+) 下,輸入操作後不能直接進行輸出操作,反之亦然,必須中間插入定位操作 (fseek/fsetpos/rewind)。

這是因為檔案流內部維護單一緩衝區與位置指標,讀寫模式切換需刷新緩衝區並重新定位。

錯誤程式碼

FILE* fp = fopen("aaa.txt", "rb+");
fread(buf, 20, 1, fp);      // 讀取操作
fwrite("test", 5, 1, fp);   // ❌ 直接寫入:未定義行為,通常失敗

正確修正

FILE* fp = fopen("aaa.txt", "rb+");
fread(buf, 20, 1, fp);           // 讀取
fseek(fp, 0, SEEK_CUR);          // ✅ 關鍵:定位操作切換模式 (SEEK_CUR 保持當前位置)
fwrite("test", 5, 1, fp);        // ✅ 現在可以正常寫入
// 或 fseek(fp, 0, SEEK_SET);    // 回到檔案開頭
// 或 fseek(fp, 0, SEEK_END);    // 移至檔案結尾

關鍵限制:SEEK_CUR 在某些實作無效

經驗顯示:必須使用 SEEK_SET 或 SEEK_END,SEEK_CUR 可能不觸發模式切換 (視 libc 實作而定)。最保險做法:

// 讀取後要寫入
fseek(fp, 0, SEEK_SET); // 或當前位置 ftell 後 SEEK_SET

// 寫入後要讀取
fseek(fp, 0, SEEK_SET);

完整修正範例

#define _CRT_SECURE_NO_WARNINGS
#include 

int main() {
    FILE* fp = fopen("aaa.txt", "rb+");
    if (!fp) return 1;

    // 移至檔案結尾取得大小
    if (_fseeki64(fp, 0, SEEK_END) != 0) { fclose(fp); return 1; }
    long long size = _ftelli64(fp);
    if (size < 0) { fclose(fp); return 1; }
    if (_fseeki64(fp, 0, SEEK_SET) != 0) { fclose(fp); return 1; }

    char buf[1024];
    if (fread(buf, 20, 1, fp) != 1) { fclose(fp); return 1; }

    // 關鍵修正:fread 後必須 fseek 才能 fwrite
    if (fseek(fp, 0, SEEK_SET) != 0) { fclose(fp); return 1; } // 或 SEEK_CUR (視實作)

    if (fwrite("test", 5, 1, fp) != 1) { 
        printf("fwrite 失敗\n"); 
        fclose(fp); 
        return 1; 
    }

    fclose(fp);
    printf("成功\n");
    return 0;
}

標準原文引用

C11 7.21.5.3/7: "When a file is opened with update mode ('+' as the second or third character in the mode argument), both input and output may be performed on the associated stream. However, output shall not be directly followed by input without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), and input shall not be directly followed by output without an intervening call to a file positioning function."

教訓總結

  • rb+/wb+/ab+ 更新模式下,讀寫切換強制需 fseek/fflush/rewind
  • errno 不一定會設定,行為屬未定義,難以除錯
  • 跨平台程式碼請明確加入 fseek(fp, 0, SEEK_SET) 確保行為一致
  • 現代 C++ 建議使用 std::fstream,RAII 管理、型別安全、避免此類陷阱
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved