Fread 到底是怎么工作的?

fread的声明如下:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

问题是: 两个这样打给 fread的电话在阅读表现上有区别吗:

char a[1000];
  1. fread(a, 1, 1000, stdin);
  2. fread(a, 1000, 1, stdin);

它每次读取 1000字节 马上吗?

82073 次浏览

根据 说明书,这两者可能会被实现区别对待。

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

当然,一些实现仍然可以根据需要将“局部”元素复制到尽可能多的字节中。

在性能上可能有,也可能没有任何区别,但在语义上是有区别的。

fread(a, 1, 1000, stdin);

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

fread(a, 1000, 1, stdin);

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

它们之所以不同,是因为 fread()返回的是它能够读取的数据元素的数量,而不是字节的数量。如果它在读取完整的1000字节之前到达文件末尾(或错误条件) ,则第一个版本必须准确指出它读取了多少字节; 第二个版本将失败并返回0。

实际上,它可能只是调用一个较低级别的函数,该函数尝试读取1000个字节,并指示它实际读取的字节数。对于较大的读操作,它可能会执行多个较低级别的调用。由 fread()返回的值的计算是不同的,但是计算的开销是微不足道的。

如果在尝试读取数据之前,实现可以告诉您没有足够的数据可读,那么可能会有不同。例如,如果您从一个900字节的文件中读取,第一个版本将读取所有900字节并返回900,而第二个版本可能不需要读取任何内容。在这两种情况下,文件位置指示器都是按照成功读取的 角色的数量(即900)前进的。

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

可能没有性能差异,但是这些调用是不一样的。

  • fread返回读取的元素数,因此这些调用将返回不同的值。
  • 如果一个元素不能被完全读取,它的值是不确定的:

如果发生错误,则流的文件位置指示器的结果值为 不确定。如果一个部分元素被读取,它的值就是不确定的。(ISO/IEC 9899: TC27.19.8.1)

Glibc 实现中没有多大区别,它只是将元素大小乘以元素的数量,以确定要读取的字节数,并将读取的数量除以最终的成员大小。但是指定元素大小为1的版本总是会告诉您读取的字节数正确。但是,如果您只关心完全读取特定大小的元素,那么使用其他表单可以避免进行除法。

http://pubs.opengroup.org/onlinepubs/000095399/functions/fread.html的另一个句子是值得注意的

Fread ()函数应该从 ptr 指向的流读入数组,直到数组中的 nitems 元素,这些元素的大小是以字节为单位指定的。对于每个对象,都应该对 fgetc ()函数和存储的结果进行 size 调用,按照读取顺序,在一个无符号字符数组中,正好覆盖对象。

在这两种情况下,数据都将由 fgetc ()访问... !

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

size_t fread (void* buf, size_t size, size_t count, FILE* f)
{
size_t bytes_requested = size * count;
size_t bytes_read = read(f->fd, buf, bytes_requested);
return bytes_read / size;
}

请注意,C 和 POSIX标准并不保证每次都需要读取大小为 size的完整对象。如果无法读取一个完整的对象(例如,stdin只有999个字节,但是您已经请求了 size == 1000) ,那么文件将处于不确定状态(C997.19.8.1/2)。

编辑: 查看关于 POSIX 的其他答案。

fread在内部调用 getc。在 Minix中,getc被调用的次数是简单的 size*nmemb,所以 getc被调用的次数取决于这两者的 getc0。因此,fread(a, 1, 1000, stdin)fread(a, 1000, 1, stdin)都将运行 getc 1000=(1000*1)次。 以下是来自 Minix 的简单实现

size_t fread(void *ptr, size_t size, size_t nmemb, register FILE *stream){
register char *cp = ptr;
register int c;
size_t ndone = 0;
register size_t s;


if (size)
while ( ndone < nmemb ) {
s = size;
do {
if ((c = getc(stream)) != EOF)
*cp++ = c;
else
return ndone;
} while (--s);
ndone++;
}


return ndone;
}

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

所有现代 C 库对于这两个调用都具有相同的性能:

fread(a, 1, 1000, file);
fread(a, 1000, 1, file);

甚至是这样的:

for (int i=0; i<1000; i++)
a[i] = fgetc(file)

应该会导致相同的磁盘访问模式,尽管由于对标准 c 库的调用更多,以及在某些情况下需要磁盘执行额外的寻道(否则这些寻道会被优化掉) ,fgetc 的速度会更慢。

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

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