我有一个相当好奇的问题,实际上并不是很实用。这个错误(在r
模式下读取二进制文件)显而易见,但我还是被其他事情搞糊涂了。
以下是代码 -
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdint.h>
#define BUFFER_LEN 512
typedef uint8_t BYTE;
int main()
{
FILE* memcard = fopen("card.raw", "r");
BYTE buffer[BUFFER_LEN];
int count = 0;
while (fread(buffer, sizeof(*buffer), BUFFER_LEN, memcard) != 0)
{
printf("count: %d\n", count++);
}
fclose(memcard);
return 0;
}
现在,card.raw
是一个二进制文件,所以这个读取过程会出错,因为使用了r
模式而不是rb
模式。但我感到很奇怪的是,那个循环恰好执行了3次,在最后一次执行中,它甚至没有读取512个字节。
如果我将该循环更改为:
while (fread(buffer, sizeof(*buffer), BUFFER_LEN, memcard) != 0)
{
printf("ftell: %ld\n", ftell(memcard));
}
现在它不再只执行3次了,实际上会一直执行直到(可能)文件结束。然而fread
计数仍然出现问题。许多读取操作并没有返回512个元素。但这很可能是由于以r
模式打开文件和所有伴随的编码错误所致。
ftell
不应该影响文件本身,那么为什么将ftell
包含在循环中会使它执行更多次呢?
我决定进一步改变循环以提取更多信息-
while ((count = fread(buffer, sizeof(*buffer), BUFFER_LEN, memcard)) != 0)
{
printf("fread bytes read: %d\n", count);
printf("ftell: %ld\n", ftell(memcard));
}
这个循环会按照预期循环相同的次数,但是如果删除循环中的
ftell
语句,则前几个结果看起来像这样-
现在如果我完全删除ftell
语句,它就会给我这个-
只有3次执行,但没有任何变化。这种行为背后的解释是什么?
注:我知道由于读取模式,
fread
和ftell
返回的计数可能不正确,但这不是我的关注点。我只是好奇——为什么包括ftell
和不包括它之间会有差异。此外,如果有帮助的话,《card.raw》文件实际上只是cs50 pset4“memory card”。可以通过
wget https://cdn.cs50.net/2019/fall/psets/4/recover/recover.zip
来获取它,并将输出文件存储在一个.zip
文件中。编辑: 我应该提到这是在Windows下使用clang工具的VS2019。命令行选项(从VS2019项目属性中检查)看起来像-
/permissive- /GS /W3 "Debug\" "Debug\" /Zi /Od "Debug\vc142.pdb" /fp:precise /D "_CRT_SECURE_NO_WARNINGS" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /WX- /Gd /MDd /Fa"Debug\" /EHsc /nologo /Fo"Debug\" /Fp"Debug\Test.pch" /diagnostics:column
编辑:此外,我在循环内部使用了ferror
,有时也尝试使用ftell
,但都没有出现任何错误。实际上,无论哪种情况,循环结束后feof
都会返回1。
编辑:我还尝试在fopen
后添加memcard == NULL
检查,结果行为相同。
编辑:为了回应@orlp的答案,事实上我确实检查了错误。不过我当然应该发布它。
while ((count = fread(buffer, sizeof(*buffer), BUFFER_LEN, memcard)) != 0)
{
if ((err = ferror(memcard)))
{
fprintf(stderr, "Error code: %d", err);
perror("Error: ");
return 1;
}
printf("fread bytes read: %d\n", count);
printf("ftell: %ld\n", ftell(memcard));
}
if ((err = ferror(memcard)))
{
fprintf(stderr, "Error code: %d", err);
perror("Error: ");
return 1;
}
这两个if
语句都没有被触发。
编辑:我之前以为已经找到了答案,是因为ftell
重置了EOF(文件结束标记)。但是我现在把循环改成了-
while ((count = fread(buffer, sizeof(*buffer), BUFFER_LEN, memcard)) != 0)
{
if ((err = ferror(memcard)))
{
fclose(memcard);
fprintf(stderr, "Error code: %d", err);
perror("Error: ");
return 1;
}
if (feof(memcard))
{
printf("reached before\n");
}
printf("fread bytes read: %d\n", count);
ftell(memcard);
if (feof(memcard))
{
printf("reached after\n");
}
}
这会触发第一个if(feof)
和第二个if(feof)
如预期所述,如果我将ftell
更改为fseek(memcard, 0, SEEK_CUR)
,EOF
被重置了,并且reached after
永远不会被打印。
card.raw
是一个二进制文件,因此如果以r
模式而不是rb
模式读取,那么读取可能会出错。但这并不一定会出错,这取决于您的平台。 - Cheatah&
,不用担心。 - Chasewhile (fread(buffer, sizeof(*buffer), BUFFER_LEN, memcard) != 0)
没有更新count
,因此这些结论基于除发布的代码之外的其他内容。我建议在每个步骤中发布使用的确切代码以改进调查。 - chux - Reinstate Monicaftell
,它会在遇到 Windows 文本模式文件中的 EOF 字符0x1a
时停止。我不知道为什么调用ftell
会改变这个行为。 - interjayfeof
,这就是问题所在。它确实被执行并且ftell
似乎将其重置。因此,0x1a
和ftell
的组合导致了问题。您能否在一个答案中解释整个过程? - Chase