为什么在 C 中 putw 扩展文件后使用 fread?
Why using fread after putw expands the file in C?
我尝试使用 fread()
从文件中读取一些数据,然后我意识到我的文件在不断增长。但是由于我是从文件中读取的,所以这种行为对我来说是不合理的。所以我写了这段代码,发现如果我使用 putw()
将数据写入文件,然后尝试从该文件读取(在关闭并重新打开文件之前),fread
扩展文件能够从中读取。
操作系统:Windows8.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
问题
代码中有一些问题可能导致 undefined behavior:
- 混合面向宽和面向字节的函数,
- 使用写入宽方向流的字符后位置的内容(导致潜在的帧错误),并且
- 在没有干预的情况下在输出函数之后调用输入函数
fflush
。
问题 2 很难用简洁的措辞表达;下面引用的 C 标准部分应该更清楚。
与方向相关的功能行为在 C17(草案)§§ 7.21.2 4,5:
中定义
4 Each stream has an orientation. After a stream is associated with an external file, but before any operations are performed on it, the stream is without orientation. Once a wide character input/output function has been applied to a stream without orientation, the stream becomes a wide-oriented stream. Similarly, once a byte input/output function has been applied to a stream without orientation, the stream becomes a byte-oriented stream. Only a call to the freopen function or the fwide[*] function can otherwise alter the orientation of a stream. (A successful call to freopen removes any orientation.)
5 Byte input/output functions shall not be applied to a wide-oriented stream and wide character input/output functions shall not be applied to a byte-oriented stream. The remaining stream operations do not affect, and are not affected by, a stream’s orientation, except for the following additional restrictions:
[…]
— For wide-oriented streams, after a successful call to a file-positioning function that leaves the file position indicator prior to the end-of-file, a wide character output function can overwrite a partial multibyte character; any file contents beyond the byte(s) written are henceforth indeterminate.
§ 7.19.5.3 6 (fopen
):
涵盖了无需冲洗的混合输出和输入
6 When a file is opened with update mode (’+’ as the second or third character in the above list of mode argument values), 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), […]
这些也列在附件 J.2 的未定义行为大列表中:
The behavior is undefined in the following circumstances:
[…]
— A byte input/output function is applied to a wide-oriented stream, or a wide character input/output function is applied to a byte-oriented stream (7.21.2).
— Use is made of any portion of a file beyond the most recent wide character written to a wide-oriented stream (7.21.2).
[…]
— An output operation on an update stream is followed by an input operation without an intervening call to the fflush function or a file positioning function, […] (7.19.5.3).
解决方案
有两种方法:
- 在宽字符函数和面向字节的函数之间使用
freopen
,或者
- 仅使用面向字节的函数(例如
fwrite
),并在写入和读取之间使用 fflush
(或 fseek
,根据标准)。
备注fwide
can only set the orientation of unoriented streams, so it can't address the issues; once the orientation of a stream is set, it can only be cleared with freopen
。
freopen
解决方案
freopen
自行解决 3 个问题中的 2 个:
- 它清除了宽函数和字节函数之间的方向,所以它们不会混合。
- 就其本身而言,
freopen
会在文件尾部留下任何垃圾字符,尽管在给定的示例中这不应该成为问题。如果这是一个问题,流必须首先是 truncated(尽管这不适合该示例)。
freopen
调用 fflush
,因此输出不会直接跟随输入。
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));
}
面向字节I/O解决方案
将 putw
替换为 fwrite
并添加对 fflush
的调用解决了所有 3 个问题:
- 不再使用宽方向函数,因此没有方向混合。
- 由于没有使用宽向功能,您不会遇到第 7.21.2 节 5 中提到的帧错误问题。
fflush
明确解决了 § 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
在玩具问题的上下文中,调用 putw
后跟 fread
与在生产中完成的事情没有多大意义(尽管这并不重要,因为它的目的是说明一个问题)。因此,上述解决方案可能无法解决混合 putw
与 fread
.
的生产代码的各个方面
示例代码中仅显示了最少的错误处理。
我尝试使用 fread()
从文件中读取一些数据,然后我意识到我的文件在不断增长。但是由于我是从文件中读取的,所以这种行为对我来说是不合理的。所以我写了这段代码,发现如果我使用 putw()
将数据写入文件,然后尝试从该文件读取(在关闭并重新打开文件之前),fread
扩展文件能够从中读取。
操作系统:Windows8.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
问题
代码中有一些问题可能导致 undefined behavior:
- 混合面向宽和面向字节的函数,
- 使用写入宽方向流的字符后位置的内容(导致潜在的帧错误),并且
- 在没有干预的情况下在输出函数之后调用输入函数
fflush
。
问题 2 很难用简洁的措辞表达;下面引用的 C 标准部分应该更清楚。
与方向相关的功能行为在 C17(草案)§§ 7.21.2 4,5:
中定义4 Each stream has an orientation. After a stream is associated with an external file, but before any operations are performed on it, the stream is without orientation. Once a wide character input/output function has been applied to a stream without orientation, the stream becomes a wide-oriented stream. Similarly, once a byte input/output function has been applied to a stream without orientation, the stream becomes a byte-oriented stream. Only a call to the freopen function or the fwide[*] function can otherwise alter the orientation of a stream. (A successful call to freopen removes any orientation.)
5 Byte input/output functions shall not be applied to a wide-oriented stream and wide character input/output functions shall not be applied to a byte-oriented stream. The remaining stream operations do not affect, and are not affected by, a stream’s orientation, except for the following additional restrictions: […]
— For wide-oriented streams, after a successful call to a file-positioning function that leaves the file position indicator prior to the end-of-file, a wide character output function can overwrite a partial multibyte character; any file contents beyond the byte(s) written are henceforth indeterminate.
§ 7.19.5.3 6 (fopen
):
6 When a file is opened with update mode (’+’ as the second or third character in the above list of mode argument values), 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), […]
这些也列在附件 J.2 的未定义行为大列表中:
The behavior is undefined in the following circumstances:
[…]
— A byte input/output function is applied to a wide-oriented stream, or a wide character input/output function is applied to a byte-oriented stream (7.21.2).
— Use is made of any portion of a file beyond the most recent wide character written to a wide-oriented stream (7.21.2).
[…]
— An output operation on an update stream is followed by an input operation without an intervening call to the fflush function or a file positioning function, […] (7.19.5.3).
解决方案
有两种方法:
- 在宽字符函数和面向字节的函数之间使用
freopen
,或者 - 仅使用面向字节的函数(例如
fwrite
),并在写入和读取之间使用fflush
(或fseek
,根据标准)。
备注fwide
can only set the orientation of unoriented streams, so it can't address the issues; once the orientation of a stream is set, it can only be cleared with freopen
。
freopen
解决方案
freopen
自行解决 3 个问题中的 2 个:
- 它清除了宽函数和字节函数之间的方向,所以它们不会混合。
- 就其本身而言,
freopen
会在文件尾部留下任何垃圾字符,尽管在给定的示例中这不应该成为问题。如果这是一个问题,流必须首先是 truncated(尽管这不适合该示例)。 freopen
调用fflush
,因此输出不会直接跟随输入。
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));
}
面向字节I/O解决方案
将 putw
替换为 fwrite
并添加对 fflush
的调用解决了所有 3 个问题:
- 不再使用宽方向函数,因此没有方向混合。
- 由于没有使用宽向功能,您不会遇到第 7.21.2 节 5 中提到的帧错误问题。
fflush
明确解决了 § 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
在玩具问题的上下文中,调用 putw
后跟 fread
与在生产中完成的事情没有多大意义(尽管这并不重要,因为它的目的是说明一个问题)。因此,上述解决方案可能无法解决混合 putw
与 fread
.
示例代码中仅显示了最少的错误处理。