我試圖從一個檔案中讀取一些資料fread(),我意識到我的檔案不斷增長。但由于我只是從檔案中讀取,這種行為對我來說是不合理的。所以我寫了這段代碼,發現如果我putw()用來將資料寫入檔案,然后嘗試從該檔案讀取(在關閉并重新打開檔案之前),fread擴展檔案以能夠從中讀取。
作業系統:Windows 8.1
編譯器:MinGW gcc
編碼:
typedef struct {
int a;
int b;
} A;
int main() {
FILE* f = fopen("file", "wb");
A a;
a.a = 2;
a.b = 3;
putw(1, f);
fwrite(&a, sizeof(A), 1, f);
fclose(f); // To make sure that wb mode and fwrite are not responsible
f = fopen("file", "rb ");
printf("initial position: %ld\n", ftell(f));
putw(1, f);
printf("position after putw: %ld\n", ftell(f));
printf("fread result: %d\n", fread(&a, sizeof(A), 1, f));
printf("position after 1st fread: %ld\n", ftell(f));
printf("fread result: %d\n", fread(&a, sizeof(A), 1, f));
printf("position after 2nd fread: %ld\n", ftell(f));
fclose(f);
remove("file");
return 0;
}
結果:
initial position: 0
position after putw: 4
fread result: 1
position after 1st fread: 12
fread result: 1
position after 2nd fread: 20
uj5u.com熱心網友回復:
問題
代碼中有一些問題可能會導致未定義的行為:
- 混合面向寬和面向位元組的功能,
- 使用位置在寫入寬定向流的字符之后的內容(導致潛在的幀錯誤),以及
- 在輸出函式之后呼叫輸入函式而無需干預
fflush。
問題 2 很難用簡潔的方式表達;下面參考的 C 標準部分應該更清楚。
與方向相關的函式行為在 C17(草案)§§ 7.21.2 4,5 中定義:
4 每個流都有一個方向。在流與外部檔案關聯之后,但在對其執行任何操作之前,流沒有方向。一旦將寬字符輸入/輸出函式應用于沒有方向的流,該流就變成了寬方向的流。類似地,一旦將位元組輸入/輸出函式應用于沒有方向的流,該流就變成了面向位元組的流。只有呼叫freopen函式或fwide [*]函式才能改變流的方向。(成功呼叫freopen會洗掉任何方向。)
5 位元組輸入/輸出函式不應應用于面向寬的流,寬字符輸入/輸出函式不應應用于面向位元組的流。除了以下附加限制外,其余的流操作不會影響流的方向,也不受其影響:[…]
— 對于面向寬的流,在成功呼叫在檔案結束之前留下檔案位置指示符的檔案定位函式后,寬字符輸出函式可以覆寫部分多位元組字符;任何超出寫入位元組的檔案內容此后都是不確定的。
§ 7.19.5.3 6 ( fopen)涵蓋沒有沖洗的混合輸出和輸入:
6 當檔案以更新模式(' ' 作為上述模式引數值串列中的第二個或第三個字符)打開時,輸入和輸出都可以在關聯的流上執行。但是,在沒有對fflush函式或檔案定位函式(fseek、fsetpos或rewind)的干預呼叫的情況下,輸出不應直接跟在輸入之后,[…]
這些也列在未定義行為的大串列中,附件 J.2:
在以下情況下,行為未定義:
[…]
— 位元組輸入/輸出功能應用于面向寬的流,或寬字符輸入/輸出功能應用于面向位元組的流(7.21.2)。
— 使用檔案的任何部分,超出寫入面向寬的流的最新寬字符 (7.21.2)。
[…]
— 更新流上的輸出操作之后是輸入操作,沒有對fflush函式或檔案定位函式的干預呼叫,[...] (7.19.5.3)。
解決方案
有兩種方法:
- 使用
freopen在寬字符和面向位元組的功能,或間 - 在寫入和讀取之間僅使用面向位元組的函式(例如
fwrite)和fflush(或fseek按照標準)。
注意fwide只能設定非定向流的方向,所以不能解決問題;一旦設定了流的方向,就只能用 清除它freopen。
freopen 解決方案
freopen 其自身解決了 3 個問題中的 2 個:
- It clears the orientation in between the wide and byte orented functions, so they're not mixed.
- On its own,
freopenwill leave any garbage characters in the tail of the file, though it shouldn't be an issue in the given example. If this is an issue, the stream must first be truncated (though this isn't appropriate for the example). freopencallsfflush, so that output is not directly followed by input.
const char* fName = "file";
f = fopen(fName, "rb ");
putw(1, f);
// truncate here, if applicable
if (freopen(NULL, "rb ", f)) {
int nA;
fread(&nA, sizeof(nA), 1, f);
printf("fread result: %d\n", fread(&a, sizeof(A), 1, f));
printf("position after 1st fread: %ld\n", ftell(f));
printf("fread result: %d\n", fread(&a, sizeof(A), 1, f));
printf("position after 2nd fread: %ld\n", ftell(f));
}
Byte-Oriented I/O Solution
Replacing putw with fwrite and adding a call to fflush addresses all 3 issues:
- No more wide-orientation functions are used, so there's no orientation mixing.
- With no wide-orientation functions being used, you don't have the problem of framing errors mentioned in § 7.21.2 5.
fflushexplicitly addresses § 7.19.5.3 6.
const char* fName = "file";
f = fopen(fName, "rb ");
int nA = 1;
fwrite(&nA, sizeof(nA), 1, f);
fflush(f);
printf("fread result: %d\n", fread(&a, sizeof(A), 1, f));
printf("position after 1st fread: %ld\n", ftell(f));
printf("fread result: %d\n", fread(&a, sizeof(A), 1, f));
printf("position after 2nd fread: %ld\n", ftell(f));
PS
In the context of the toy problem, the call to putw followed by fread doesn't make much sense as something that would be done in production (though that's not as important, as its purpose is to illustrate an issue). As such, the above solutions might not address aspects of production code that mixes putw with fread.
Only minimal error handling is shown in the sample code.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/318463.html
上一篇:讀取檔案并放入多維陣列時出現垃圾
下一篇:不同的讀寫指標
