fread如何真正起作用?


75

的声明fread如下:

问题是:两次调用fread

  1. fread(a, 1, 1000, stdin);
  2. fread(a, 1000, 1, stdin);

一次读取一次1000字节吗?

Answers:


106

性能可能有差异,也可能没有差异。语义上有所不同。

尝试读取1000个数据元素,每个元素长1个字节。

尝试读取1个1000字节长的数据元素。

它们不同,因为fread()返回的是它能够读取的数据元素的数量,而不是字节的数量。如果在读取完整的1000个字节之前到达文件末尾(或错误状态),则第一个版本必须确切说明读取的字节数。第二个只是失败并返回0。

实际上,可能只是调用一个较低级别的函数,该函数尝试读取1000个字节并指示实际读取了多少个字节。对于较大的读取,它可能会发出多个较低级别的调用。要返回的值的计算fread()方法不同,但是计算费用微不足道。

如果实现可以在尝试读取数据之前告诉您没有足够的数据可读取,则可能会有所不同。例如,如果您正在读取900字节的文件,则第一个版本将读取所有900字节并返回900,而第二个版本可能不会费心地读取任何内容。在这两种情况下,文件位置指示器都以成功读取的字符数(即900 )前移。

但是通常,您可能应该根据需要的信息来选择如何调用它。如果部分读取并不比完全不读取任何数据好,则读取单个数据元素。如果部分读取很有用,则以较小的块读取。


第二个可能不会理会任何东西。在这两种情况下,文件位置指示器都以成功读取的字符数为单位前进,即900是否不是因为在第二个版本中文件位置指示器由于没有读取任何内容而没有前进?换句话说,不应该fread(a, 1000, N, stdin);总是将fp指标提高1000?的倍数吗?
Shahbaz 2014年

1
没关系,找到了。C11在7.21.8.1.2和7.21.8.2.2中说:如果发生错误,则流的文件位置指示符的结果值不确定。
Shahbaz 2014年

所以没有办法恢复指标的位置吗?还是要避免阅读与位置指示器混淆的最后一个块?
David天宇Wong

1
@David天宇Wong:如果您需要恢复职位,ftell请先致电fread,然后致电fseek
Keith Thompson

2
POSIX规范更加严格...它要求fread的确确定每个对象fgetc的大小,因此在两种情况下都将执行完全相同数量的fgetc(但返回值将不同)。
吉姆·巴尔特

17

根据规范,两者的实现方式可能会有所不同。

如果文件少于1000个字节,fread(a, 1, 1000, stdin)(读取1000个元素,每个元素1个字节)仍将复制所有字节,直到EOF。另一方面,未指定fread(a, 1000, 1, stdin)存储(读取1个1000字节元素)的结果a,因为没有足够的数据来完成对“第一个”(也是唯一的)1000字节元素的读取。

当然,某些实现可能仍会根据需要将“ partial”元素复制到任意多个字节中。


15

那将是实现细节。glibc中,两者在相同的性能,因为它基本上实现为(参考http://sourceware.org/git/?p=glibc.git;a=blob;f=libio/iofread.c):

注意C 和POSIX标准不保证size每次都需要阅读完整的尺寸对象。如果无法读取完整的对象(例如,stdin只有999个字节,但您已请求size == 1000),则文件将处于不确定状态(C99§7.19.8.1/ 2)。

编辑:请参阅有关POSIX的其他答案。


您提到了POSIX标准,但它要求fread必须以fgetc的方式实现,它比C要求更具确定性。
吉姆·巴尔特

1
真棒.. !! 正是每个登陆这里的人都需要的.. !!! 我很惊讶它获得了如此多的票..
mk ..

fwrite也一样吗?
mk..14

重要提示:读取大于1个大小的记录时,您可以中断文件。
ArekBulski

5

freadgetc内部通话。在Minix次数getc被称为仅仅是size*nmemb那么多少次getc将被称为依赖于产品的这两个。所以无论fread(a, 1, 1000, stdin)fread(a, 1000, 1, stdin)运行getc 1000=(1000*1)时间。这是freadMinix的简单实现


1
我认为是真正的答案
Sathvik '20年

3

性能可能没有差异,但是这些调用并不相同。

  • fread 返回读取的元素数,因此这些调用将返回不同的值。
  • 如果无法完全读取元素,则其值是不确定的:

如果发生错误,则流的文件位置指示符的结果值不确定。如果读取了部分元素,则其值不确定。(ISO / IEC 9899:TC2 7.19.8.1)

glibc实现没有太大的区别,该实现只是将元素大小乘以元素数来确定要读取的字节数,最后将读取的数量除以成员大小。但是,将元素大小指定为1的版本将始终告诉您读取的正确字节数。但是,如果您只关心完全读取一定大小的元素,则使用其他形式可以避免进行除法。


1

还有一个句子形式http://pubs.opengroup.org/onlinepubs/000095399/functions/fread.html值得注意

fread()函数应从stream指向的流中读取ptr指向的数组,直到nitems个元素,其大小由以字节为单位的大小指定。对于每个对象,应调用fgetc()函数进行大小调用,并将结果按读取顺序存储在与对象完全重叠的无符号字符数组中。

简而言之,两种情况下数据都可以通过fgetc()进行访问!


是的,我也有这种感觉,但在该页面上写道:“此参考页面上描述的功能与ISO C标准保持一致。” 似乎有疑问?
Jeegar Patel 2011年

@ Mr.32:该标准在对的调用中也有同样的说法fgetc,因此Posix确实与C99保持一致。但是该标准并没有为符合标准的程序提供任何方法来确定是否fgetc“真正”被调用,或者是否fread具有其他等同功能。5.1.2.3解释说,该标准仅描述了“抽象机器”的行为,并列出了实际程序必须以何种方式匹配该行为。这在C ++中被称为“假设”规则,但在C中则没有(我之前的错误)。不可观察的行为不必相同。
史蒂夫·杰索普

因此,即使特定的实现为您提供了一些方法来计算fgetc调用次数(也许通过使您的程序与该函数自己的版本链接,例如通过修改和重新编译libc),也可以通过警告来做到这一点。并非总是调用您要替换的函数,只有当标准将抽象机描述为调用它时,才调用该函数。
史蒂夫·杰索普

@SteveJessop“不可观察的行为不必相同。” 那么为什么要在POSIX中进行记录呢?
罗曼·拜什科(Royal Byshko)2011年

@Beginner:因为对抽象机行为的描述是描述fread(或C代码的其他任何部分)效果的便捷方法。在Posix中以这种方式进行记录只是因为在标准中以这种方式进行了记录。
史蒂夫·杰索普

1

我想在这里澄清答案。fread执行缓冲的IO。实际使用的读取块大小由使用的C实现确定。

通过以下两个调用,所有现代C库都将具有相同的性能:

甚至像:

应该会得到相同的磁盘访问模式,尽管由于更多的对标准c库的调用而使fgetc变慢,并且在某些情况下需要磁盘来执行其他搜索,而磁盘会另外被优化。

回到两种形式的区别。前者返回读取的实际字节数。如果文件大小小于1000,则后者返回0,否则返回1。在两种情况下,缓冲区都将填充相同的数据,即文件内容最多1000个字节。

通常,您可能希望将2nd参数(大小)设置为1,以便获得读取的字节数。


“所有现代C库在这两次调用中将具有相同的性能” –是的。“在某些情况下,需要磁盘执行其他搜索,而这些搜索本来可以被优化掉的” –不。fgetc只是从stdio的内存缓冲区中读取。即使将流设置为不缓冲,底层操作系统也会缓冲磁盘读取。
吉姆·巴尔特

@Jim:fgetc以不同于fread的方式从stdio读取。显而易见的结果是,fgetc总是会最大化查找/系统调用的数量(错误),而fread会在您为libc提供有关正在执行的操作的更多信息时最小化查找/系统调用的数量。
Clarus 2014年

1
抱歉,但是您不知道您在说什么... fread或fgetc不会有任何不同,这会影响搜索次数,并且您也没有为这种荒谬的主张提供任何支持。请注意,C99和POSIX标准中fread的定义是以fgetc的形式给出的,如本页其他地方所述。
吉姆·巴尔特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.