fread和fwrite函数,C语言fread和fwrite函数详解

对文件格式化读写函数 fprintf 与 fscanf 而言,尽管它可以从磁盘文件中读写任何类型的文件,即读写的文件类型可以是文本文件、二进制文件,也可以是其他形式的文件。但是,对二进制文件的读写来说,考虑到文件的读写效率等原因,还是建议尽量使用 fread 和 fwrite 函数进行读写操作。

fread 与 fwrite 函数的原型如下面的代码所示:

size_t fread(void *buf, size_t size, size_t count, FILE *fp);
size_t fwrite(const void * buf, size_t size, size_t count, FILE *fp);

在上面的 fread 和 fwrite 函数原型中:
  • 参数 size 是指单个元素的大小(其单位是字节而不是位,例如,读取一个 int 型数据就是 4 字节);
  • 参数 count 指出要读或写的元素个数,这些元素在 buf 所指的内存空间中连续存放,共占“size*count”个字节。

即 fread 函数从文件 fp 中读出“size*count”个字节保存到 buf 中,而 fwrite 把 buf 中的“size*count”个字节写到文件 fp 中。最后,函数 fread 和 fwrite 的返回值为读或写的记录数,成功时返回的记录数等于 count 参数,出错或读到文件末尾时返回的记录数小于 count,也可能返回 0。

需要注意的是,尽管 fread 和 fwrite 函数可以对数据进行成块读写,但并不是说一次想读写多少数据就能全部读写多少数据,毕竟缓存有限,而且不同的操作系统的缓存大小也可能不一样。同时,许多程序员还认为函数的参数 (size、count) 与位置对齐有关,甚至认为语句“fwrite(ptr,1,1024,fp)”的执行效率会比“fwrite(ptr,1024,1,fp)”高。实际情况并非如此,如在 glibc-2.17 库中对 fwrite 函数的实现如下:
_IO_size_t _IO_fwrite (const void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
    _IO_size_t request = size * count;
    _IO_size_t written = 0;
    CHECK_FILE (fp, 0);
    if (request == 0)
        return 0;
    _IO_acquire_lock (fp);
    if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
        written = _IO_sputn (fp, (const char *0 buf, request);
    _IO_release_lock (fp);
    if (written == request)
        return count;
    else if (written == EOF)
        return 0;
    else
        return written / size;
}
从上面的 fwrite 函数源码实现中可以清楚地看到:

首先,在把参数 size 与 count 传进函数之后,第一步就是通过语句“_IO_size_t request=size*count;”来计算“size*count”,所以这两个参数与什么位置对齐根本没有半点关系。

其次,在函数返回时,如果整个写入成功(“written==request”),就返回 count;如果遇到 EOF(“written==EOF”),就返回 0;否则返回“written/size”。由此可见,函数返回的是成功写入的块数,而不是字节数(除非 size 为 1),这样做有许多好处。例如,在写入多个结构体时,返回值能告诉你成功写入的结构体的个数。当然,这样看来,前面的“fwrite(ptr,1,1024,fp)”与“fwrite(ptr,1024,1,fp)”语句还是有所差别的。但是,如果调用者只关心是否全部写入成功,那么就完全没必要纠结于语句“fwrite(ptr,1,1024,fp)”与“fwrite(ptr,1024,1,fp)”之间的差别了。

对于 fread 函数,其道理与 fwrite 函数完全一样,如下面的函数源代码所示:
_IO_size_t _IO_fread (void *buf,_IO_size_t size,_IO_size_t count,_IO_FILE *fp)
{
    _IO_size_t bytes_requested = size * count;
    _IO_size_t bytes_read;
    CHECK_FILE (fp, 0);
    if (bytes_requested == 0)
        return 0;
    _IO_acquire_lock (fp);
    bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
    _IO_release_lock (fp);
    return bytes_requested == bytes_read ? count : bytes_read / size;
}
除此之外,函数 fwrite 还与文件的打开模式有关。例如,如果文件的打开模式是“w+”,则是从文件指针指向的地址开始写,替换掉之后的内容,文件的长度可以不变,fp 的位置移动 count 个数;如果文件的打开模式为“a+”,则从文件的末尾开始添加,文件的长度会不断增加。