fread的声明如下:
fread
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
问题是: 两个这样打给 fread的电话在阅读表现上有区别吗:
char a[1000];
fread(a, 1, 1000, stdin);
fread(a, 1000, 1, stdin);
它每次读取 1000字节 马上吗?
1000
根据 说明书,这两者可能会被实现区别对待。
如果文件小于1000字节,则 fread(a, 1, 1000, stdin)(每个字节读取1000个元素)仍将复制所有字节,直到 EOF。另一方面,存储在 a中的 fread(a, 1000, 1, stdin)(读取11000字节的元素)的结果是未指定的,因为没有足够的数据来完成读取“第一个”(也只有)1000字节的元素。
fread(a, 1, 1000, stdin)
a
fread(a, 1000, 1, stdin)
当然,一些实现仍然可以根据需要将“局部”元素复制到尽可能多的字节中。
在性能上可能有,也可能没有任何区别,但在语义上是有区别的。
尝试读取1000个数据元素,每个数据元素长度为1字节。
尝试读取1个长度为1000字节的数据元素。
它们之所以不同,是因为 fread()返回的是它能够读取的数据元素的数量,而不是字节的数量。如果它在读取完整的1000字节之前到达文件末尾(或错误条件) ,则第一个版本必须准确指出它读取了多少字节; 第二个版本将失败并返回0。
fread()
实际上,它可能只是调用一个较低级别的函数,该函数尝试读取1000个字节,并指示它实际读取的字节数。对于较大的读操作,它可能会执行多个较低级别的调用。由 fread()返回的值的计算是不同的,但是计算的开销是微不足道的。
如果在尝试读取数据之前,实现可以告诉您没有足够的数据可读,那么可能会有不同。例如,如果您从一个900字节的文件中读取,第一个版本将读取所有900字节并返回900,而第二个版本可能不需要读取任何内容。在这两种情况下,文件位置指示器都是按照成功读取的 角色的数量(即900)前进的。
但是一般来说,您可能应该根据需要的信息来选择如何调用它。如果部分读取并不比完全不读取任何内容更好,那么读取单个数据元素。如果部分读取有用,则以较小的块读取。
可能没有性能差异,但是这些调用是不一样的。
如果发生错误,则流的文件位置指示器的结果值为 不确定。如果一个部分元素被读取,它的值就是不确定的。(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)。
size
stdin
size == 1000
编辑: 查看关于 POSIX 的其他答案。
fread在内部调用 getc。在 Minix中,getc被调用的次数是简单的 size*nmemb,所以 getc被调用的次数取决于这两者的 getc0。因此,fread(a, 1, 1000, stdin)和 fread(a, 1000, 1, stdin)都将运行 getc 1000=(1000*1)次。 以下是来自 Minix 的简单实现
getc
Minix
size*nmemb
1000=(1000*1)
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,以便获得所读取的字节数。