ftell如何影响二进制文件以'r'而不是'rb'模式读取?

问题描述 投票:5回答:1

我有一个相当好奇的问题,其实不是很实用。错误(以'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 results

现在如果我把它去掉 ftell 行完全,它给我-

without ftell results

只执行了3次,却没有任何变化。

这种行为背后有什么解释?

注:我知道这两个函数返回的计数。freadftell 可能是由于读取模式而导致的错误,但这不是我关心的问题。我只是很好奇--为什么在包括了 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)

但正如所料,如果我把 ftellfseek(memcard, 0, SEEK_CUR)జజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజ EOF 复位和 reached after 是从来没有印刷过的。

c file binary
1个回答
4
投票

正如一些评论员所指出的,它遇到了一个问题。EOFftell 其实摆脱了那个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 改变程序的行为。


1
投票

fread()

fread函数返回成功读取的元素数量,如果遇到读取错误或文件结束,该数量可能小于nmemb。

count < BUFFER_LEN,OP报告 feof() 是真的-----------------------------------如期而至。

出乎意料的是,下面一个 fread() 返回非零。

IMO,一个不兼容的库。

(OP报告了新的信息,所以这个答案现在不完整。)

看来 ftell(),不正确的IMO,重置流的文件结束指示器,允许额外的读取发生。

© www.soinside.com 2019 - 2024. All rights reserved.