我有一个相当好奇的问题,其实不是很实用。错误(以'r'模式读取二进制文件)。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
模式和所有的编码错误都伴随着.NET模式。
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
而不包括它。
另外,为防万一,The card.raw
文件其实只是cs50 pset4的 "存储卡"。您可以通过以下方式获得它 wget https://cdn.cs50.net/2019/fall/psets/4/recover/recover.zip
并将输出文件存储在 .zip
编辑:我应该说这是在windows上,使用VS2019的clang工具。命令行选项(从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。
编辑: 我还尝试在循环中添加一个 memcard == NULL
紧接着 fopen
,同样的行为。
编辑:针对@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;
}
这2个问题都没有 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
是从来没有印刷过的。
正如一些评论员所指出的,它遇到了一个问题。EOF
和 ftell
其实摆脱了那个EOF。为什么呢?要想找到答案,我们必须查看glibc的源代码。我们可以找到 来源于 ftell
::
long int
_IO_ftell (FILE *fp)
{
off64_t pos;
CHECK_FILE (fp, -1L);
_IO_acquire_lock (fp);
pos = _IO_seekoff_unlocked (fp, 0, _IO_seek_cur, 0);
if (_IO_in_backup (fp) && pos != _IO_pos_BAD)
{
if (_IO_vtable_offset (fp) != 0 || fp->_mode <= 0)
pos -= fp->_IO_save_end - fp->_IO_save_base;
}
_IO_release_lock (fp);
if (pos == _IO_pos_BAD)
{
if (errno == 0)
__set_errno (EIO);
return -1L;
}
if ((off64_t) (long int) pos != pos)
{
__set_errno (EOVERFLOW);
return -1L;
}
return pos;
}
libc_hidden_def (_IO_ftell)
weak_alias (_IO_ftell, ftell)
这是很重要的一行。
pos = _IO_seekoff_unlocked (fp, 0, _IO_seek_cur, 0);
让我们找到 来源于 _IO_seekoff_unlocked
:
off64_t
_IO_seekoff_unlocked (FILE *fp, off64_t offset, int dir, int mode)
{
if (dir != _IO_seek_cur && dir != _IO_seek_set && dir != _IO_seek_end)
{
__set_errno (EINVAL);
return EOF;
}
/* If we have a backup buffer, get rid of it, since the __seekoff
callback may not know to do the right thing about it.
This may be over-kill, but it'll do for now. TODO */
if (mode != 0 && ((_IO_fwide (fp, 0) < 0 && _IO_have_backup (fp))
|| (_IO_fwide (fp, 0) > 0 && _IO_have_wbackup (fp))))
{
if (dir == _IO_seek_cur && _IO_in_backup (fp))
{
if (_IO_vtable_offset (fp) != 0 || fp->_mode <= 0)
offset -= fp->_IO_read_end - fp->_IO_read_ptr;
else
abort ();
}
if (_IO_fwide (fp, 0) < 0)
_IO_free_backup_area (fp);
else
_IO_free_wbackup_area (fp);
}
return _IO_SEEKOFF (fp, offset, dir, mode);
}
基本上,它只是做一些检查,然后调用 _IO_SEEKOFF
所以 究其根源:
/* The 'seekoff' hook moves the stream position to a new position
relative to the start of the file (if DIR==0), the current position
(MODE==1), or the end of the file (MODE==2).
It matches the streambuf::seekoff virtual function.
It is also used for the ANSI fseek function. */
typedef off64_t (*_IO_seekoff_t) (FILE *FP, off64_t OFF, int DIR,
int MODE);
#define _IO_SEEKOFF(FP, OFF, DIR, MODE) JUMP3 (__seekoff, FP, OFF, DIR, MODE)
所以,基本上。ftell
最后调用一个函数,该函数相当于 fseek(fp, 0, SEEK_CUR)
. 而在 fseek
我们看到的标准。"一个成功的电话 fseek()
函数清除流的文件结束指示器。" 这就是为什么 ftell
改变程序的行为。
fread()
有
fread函数返回成功读取的元素数量,如果遇到读取错误或文件结束,该数量可能小于nmemb。
当 count < BUFFER_LEN
,OP报告 feof()
是真的-----------------------------------如期而至。
出乎意料的是,下面一个 fread()
返回非零。
IMO,一个不兼容的库。
(OP报告了新的信息,所以这个答案现在不完整。)
看来 ftell()
,不正确的IMO,重置流的文件结束指示器,允许额外的读取发生。