25-控制缓冲
3.13 控制缓冲
标准I/O实现了三种类型的用户缓冲,并为开发者提供了接口,可以控制缓冲区类型和大小。以下不同类型的用户缓冲提供不同功能,并适用于不同的场景:
无缓冲(Unbuffered)
不执行用户缓冲。数据直接提交给内核。因为这种无缓冲模式不支持用户缓冲(而用户缓冲一般会带来很多好处),通常很少使用,只有一个例外:标准错误默认是采用无缓冲模式。
行缓冲(Line-buffered)
缓冲是以行为单位执行。每遇到换行符,缓冲区就会被提交到内核。行缓冲对把流输出到屏幕时很有用,因为输出到屏幕的消息也是通过换行符分隔的。因此,行缓冲是终端的默认缓冲模式,比如标准输出。
块缓冲(Block-buffered)
缓冲以块为单位执行,每个块是固定的字节数。本章一开始讨论的缓冲模式即块缓冲,它很适用于处理文件。默认情况下,和文件相关的所有流都是块缓冲模式。标准I/O称块缓冲为“完全缓冲(full buffering)”。
大部分情况下,默认的缓冲模式即对于特定场景是最高效的。但是,标准I/O还提供了一个接口,可以修改使用的缓冲模式:
setbuf()函数把指定stream的缓冲模式设置成mode模式,mode值必须是以下之一:
_IONBF 无缓冲。
_IOLBF 行缓冲。
_IOFBF 块缓冲。
在 _ IONBF无缓冲模式下,会忽略参数buf和size;对于其他模式,buf可以指向一个size字节大小的缓冲空间,标准I/O会用它来执行对给定流的缓冲。如果buf为空,glibc会自动分配指定size的缓冲区。
setvbuf()函数必须在打开流后,并在执行任何操作之前被调用。成功时,返回0;出错时,返回非0值。
在关闭流时,其使用的缓冲区必须存在。一个常见的错误是把缓冲区定义成某个作用域中的一个局部变量,在关闭流之前就退出这个作用域了。特别需要注意的是,不要在main()函数中定义一个局部缓冲区,且没有显式关闭流。比如以下代码存在bug:
这类错误可以通过两种方式来解决:一是在离开作用域之前显式关闭流,二是把buf设置成全局变量。
总体而言,开发者不需要关心如何在流上使用缓冲。对于标准出错,终端是采用行缓冲模式;对于文件,采用块缓冲模式。默认的块缓冲区大小是BUFSIZ,在<stdio.h>中定义,而且通常情况下该大小设置是最优的(一个较大值,是标准块大小的整数倍)。