在讨论 setvbuf 与 setbuf 函数之前,先来看如下一段示例代码:
int main(void)
{
FILE* fp=NULL;
int fd;
const char *f1="testfprintf.log";
const char *f2="testwrite.log";
fp = fopen(f1, "wb");
if(fp == NULL)
{
return -1;
}
fd = open(f2, O_WRONLY|O_CREAT|O_EXCL, 0666);
if(fd < 0)
{
return -1;
}
while(1)
{
fprintf(fp, "fprintf------|\n");
write(fd, "write|\n", sizeof("write|\n"));
sleep(1);
}
return 0;
}
在上面的示例代码中,使用 fprintf 函数对文件 testfprintf.log 执行写入操作,使用 write 函数对文件 testwrite.log 执行写入操作。这里需要注意的是,因为 fprintf 函数会缓冲 4096 字节的数据,只有当达到这么多字节的数据时才会进行实际的磁盘写入。
因此,运行上面的示例程序,然后实时查看 testfprintf.log 文件与 testwrite.log 文件,会发现 testfprintf.log 文件不会被实时写入,只有当写入的数据的大小为 4096 字节的倍数的时候才会被写入;而 write 函数则不同,因为它不进行任何缓冲(直接写入磁盘),所以文件 testwrite.log 不断有数据写入,运行结果如图 1 所示。
在上面的示例中不难发现,通过提供缓冲区可以尽可能减少 read 和 write 调用的次数,从而降低执行 I/O 的时间。在 C 语言中,标准 I/O 库提供了 3 种类型的缓冲。
在进行 I/O 操作时,只有当 I/O 缓冲区被填满时,才进行实际的 I/O 操作。对于驻留在磁盘上的文件,通常就是由标准 I/O 库来实施全缓冲的。在一个流上执行第一次 I/O 操作时,相关标准 I/O 函数通常调用 malloc 来获得需要使用的缓冲区。
在默认情况下,全缓冲的缓冲区可以由标准 I/O 例程自动刷新。当然,也可以通过调用 fflush 函数来强制刷新一个数据流。但需要特别注意的是,在标准 I/O 库方面,flush 函数意味着将缓冲区中的内容写到磁盘上;而在终端驱动程序方面,flush 函数则表示丢弃已存储在缓冲区中的数据。
在这种情况下,只有当在输入和输出中遇到换行符时,才执行实际的 I/O 作。当然,因为标准 I/O 库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,即使还没有写一个换行符,也必须进行 I/O 操作。
很显然,它允许我们一次输出一个字符(如 fputc 函数),但只有在写完一行之后才进行实际 I/O 操作。当流涉及一个终端时,通常使用行缓冲。例如,使用最频繁的 printf 函数就是采用行缓冲,所以感觉不出缓冲的存在。
标准 IO 库不对字符进行缓冲存储。在一般情况下,标准错误流 stderr 通常也是不带缓冲的。
相对于这些系统默认的情况,也可以通过调用标准库函数 setbuf 和 setvbuf 来更改缓冲类型。函数 setbuf 和 setvbuf 将使得在打开文件后用户可以建立自己的文件缓冲区,而不使用由 fopen 函数打开文件所设定的默认缓冲区。函数 setbuf 和 setvbuf 的一般函数原型如下所示:
使用 setbuf 与 setvbuf 函数指定文件的缓冲区一定要在文件读写之前。一旦用户自己指定了文件的缓冲区,文件的读写就要在用户指定的缓冲区中进行,而不在系统默认指定的缓冲区中进行。
对于 setbuf 函数,当指定参数 buf 为 null 时,setbuf 函数将使得文件 I/O 不带缓冲。如下面的示例代码所示:
setbuf(fp, NULL);
对 setvbuf 函数来说,由于 setbuf 函数没有返回值,因此也无法确定 setbuf 函数的调用是否成功。在实际使用中,应该尽量使用 setvbuf 来替换 setbuf 函数,以验证流被成功地更改。如下面的示例代码所示:
if (setvbuf(file, buf, buf ? _IOFBF : _IONBF, BUFSIZ) != 0)
{
}
对 setvbuf 函数,则由 malloc 函数来分配缓冲区,参数 size 指明了缓冲区的长度(必须大于 0),而参数 mode 则表示缓冲的类型,取值如下所示: