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
字节吗?
性能上可能有差异,语义上有区别。
fread(a, 1, 1000, stdin);
尝试读取1000个数据元素,每个元素长度为1字节。
fread(a, 1000, 1, stdin);
尝试读取一个长度为1000字节的数据元素。
它们之间的差异在于fread()
返回它能够读取的数据元素数量,而不是字节数。如果在读取完整的1000字节之前达到文件结尾(或出现错误),第一种情况必须指示实际读取了多少字节;第二种情况将失败并返回0。
实际上,它可能只会调用低级别的函数来尝试读取1000字节,并指示实际读取了多少字节。对于更大的读取,它可能会进行多个低级别的调用。计算fread()
要返回的值是不同的,但计算的开销微不足道。
如果在尝试读取数据之前实现可以确定没有足够的数据可读,则可能存在差异。例如,如果您从一个900字节的文件中读取,第一种情况将读取所有900字节并返回900,而第二种情况可能不会做任何事情。在两种情况下,文件位置指示器都会根据成功读取的字符数(即900)进行移动。
但通常,您应该根据需要从中选择调用它的方式。如果部分读取比不读取任何内容更好,则读取单个数据元素。如果部分读取有用,则以较小的块进行读取。
那将是一个实现细节。在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
),则文件将处于不确定状态(C99 §7.19.8.1/2)。
编辑:请参见其他答案关于POSIX的内容。
fread
1MB字节的要求之前,read
可能会被多次调用,然后才会返回fread
。 - John根据规范,实现方式可能会对这两种情况进行不同的处理。
如果您的文件小于1000个字节,则fread(a, 1, 1000, stdin)
(每次读取一个字节并重复1000次)仍将复制所有字节直到EOF。另一方面,fread(a, 1000, 1, stdin)
(读取1个1000字节的元素)存储在a
中的结果是未指定的,因为没有足够的数据来完成读取“第一个”(也是唯一的)1000字节元素。
当然,有些实现可能仍会将“部分”的元素复制到所需的字节数中。
fread
在内部调用getc
。在Minix
中,getc
被调用的次数就是size*nmemb
,因此getc
被调用的次数取决于这两个数的乘积。因此,fread(a, 1, 1000, stdin)
和fread(a, 1000, 1, stdin)
都会运行getc
1000=(1000*1)
次。以下是来自Minix的fread
的简单实现。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;
}
从http://pubs.opengroup.org/onlinepubs/000095399/functions/fread.html中还有一个值得注意的句子
fread()函数将从指向流的指针中读取大小为size字节的nitems个元素到ptr指向的数组中。对于每个对象,将调用size次fgetc()函数并将结果按读取顺序存储在一个无符号字符数组中,正好覆盖该对象。
简而言之,在两种情况下都将通过fgetc()访问数据...!
fgetc
的调用也是一样的,所以Posix确实与C99对齐。但标准并没有为符合规范的程序提供任何方法来确定是否“真正”调用了fgetc
,或者fread
是否执行了其他等效操作。5.1.2.3解释了标准仅描述了“抽象机器”的行为,并列出了实际程序必须匹配该行为的方式。这在C++中被称为“as-if”规则,而不是C(我之前的错误)。非可观察行为无需相同。 - Steve Jessopfgetc
被调用的次数(例如通过让您链接程序到自己版本的该函数,例如通过修改和重新编译libc),它也可以这样做,但前提是您替换的函数不总是并且仅在标准描述抽象机器调用它时才被调用。 - Steve Jessopfread
(或任何其他C代码)效果的便捷方式。这在Posix中是以这种方式记录的,仅仅是因为标准是这样记录的。 - Steve Jessop我想澄清这里的答案。fread执行缓冲IO。fread使用的实际读取块大小由所使用的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会更慢。
回到两种形式的fread之间的区别。前者返回实际读取的字节数。后者如果文件大小小于1000,则返回0,否则返回1。在两种情况下,缓冲区都将填充相同的数据,即文件内容最多1000个字节。
一般来说,您可能希望保持第二个参数(大小)设置为1,以便获得读取的字节数。
fread(a, 1000, N, stdin);
是否总是将fp指示器按1000
的倍数推进? - Shahbazfread
前调用ftell
,然后在其后调用fseek
。 —— @David天宇Wong - Keith Thompson