fread如何真正起作用?

时间:2011-12-21 11:57:03

标签: c 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);
  3. 每次都会一次读取1000字节 吗?

7 个答案:

答案 0 :(得分:98)

表现可能有差异,也可能没有差异。语义有所不同。

fread(a, 1, 1000, stdin);

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

fread(a, 1000, 1, stdin);

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

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

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

如果实现可以在尝试读取数据之前判断没有足够的数据可供读取,则可能存在差异。例如,如果您正在读取900字节的文件,则第一个版本将读取所有900个字节并返回900,而第二个版本可能无法读取任何内容。在这两种情况下,文件位置指示符都按成功读取的字符的数量提前,即900。

但总的来说,你应该根据你需要的信息选择如何调用它。如果部分读取并不比不读取任何内容更好,则读取单个数据元素。如果部分读取有用,请读入较小的块。

答案 1 :(得分:16)

根据the specification,实施可以区别对待这两者。

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

当然,某些实现仍然可以将'partial'元素复制到所需的字节数。

答案 2 :(得分:13)

这将是实施细节。在glibc中,两者在性能上是相同的,因为它基本上实现为(Ref 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)。 / p>

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

答案 3 :(得分:3)

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

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

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

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

答案 4 :(得分:3)

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;
}

答案 5 :(得分:1)

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

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

两个案例数据中的inshort将由fgetc()...!

访问

答案 6 :(得分:0)

我想澄清这里的答案。 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)

应该导致相同的磁盘访问模式,尽管fgetc会因为对标准c库的更多调用而变慢,并且在某些情况下需要磁盘执行额外的搜索,否则这些搜索将被优化掉。

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

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