UNIX缓冲机制

某日一朋友写了一个HELLO WORLD代码,出不来结果,代码如下:

#include <stdio.h>

int  main(int argc, char **argv)

{

    printf("hello world!");

    _Exit(0);

} 

 注意到,在代码中printf语句打印的字符串最后没有带换行符,而且最后调用了_Exit函数,这导致了在终端屏幕上显示不出来字符串"hello world!"。

首先介绍一下UNIX里面关于标准IO的几种缓冲机制:

1、全缓冲 。全缓冲指的是系统在填满标准IO缓冲区之后才进行实际的IO操作;注意,对于驻留在磁盘上的文件来说通常是由标准IO库实施全缓冲。

2、行缓冲 。在这种情况下,标准IO在输入和输出中遇到换行符时执行IO操作;注意,当流涉及终端的时候,通常使用的是行缓冲。

3、无缓冲 。无缓冲指的是标准IO库不对字符进行缓冲存储;注意,标准出错流stderr通常是无缓冲的。 

 

其次介绍一下几个退出函数:

1、exit ()。调用exit函数之后,它首先会执行一系列的清理处理,隐含会对存储在缓冲区内的数据进行冲洗,包括调用执行各终止处理程序,关闭所有标准IO流,然后进入内核。

2、_exit ()。与exit不同的是,它不进行清理工作而直接进入内核。此函数由POSIX.1说明,放在unistd.h里面。

3、_Exit ()。同样,它也不进行清理工作而直接进入内核。此函数跟exit一样由ISO C说明,放在stdlib.h里面。

 

现在回过头来看上面的那段代码,很容易发现,由于printf函数是行缓冲的(因为它要往终端输出数据),而且要打印的字符串不带换行符,因此在它没有遇到换行符或者没有填满缓冲区之前不会进行实际的IO操作,而紧接下来的_Exit函数又立即进入内核没有处理IO缓冲区,所以我们在终端上看不到hello world语句

 

下面介绍设置文件缓冲区函数

  void setbuf(FILE *stream,char *buf);

  void setvbuf(FILE *stream,char *buf,int type,unsigned size);

  这两个函数将使得打开文件后,用户可建立自己的文件缓冲区,而不使用fopen()函数打开文件设定的默认缓冲区。  

  对于setbuf()函数,buf指出的缓冲区长度由头文件stdio.h中定义的宏BUFSIZE的值决定,缺省值为512字节。当选定buf为空时,setbuf函数将使的文件I/O不带缓冲。而对setvbuf函数,则由malloc函数来分配缓冲区。参数size指明了缓冲区的长度(必须大于0),而参数type则表示了缓冲的类型,其值可以取如下值:

type 值 含义

  _IOFBF 文件全部缓冲,即缓冲区装满后,才能对文件读写

  _IOLBF 文件行缓冲,即缓冲区接收到一个换行符时,才能对文件读写

  _IONBF 文件不缓冲,此时忽略buf,size的值,直接读写文件,不再经过文件缓冲区缓冲

在《C陷阱和缺陷》一书中,讲到一个setbuf的陷阱:

程序输出有两种方式:一种是即时处理方式,另一种是先暂存起来,然后再大块写入的方式,前者往往造成较高的系统负担。因此,c语言实现通常都允许程序员进行实际的写操作之前控制产生的输出数据量。

这种控制能力一般是通过库函数setbuf实现的。如果buf是一个大小适当的字符数组,那么:

setbuf(stdout,buf);

语句将通知输入/输出库,所有写入到stdout的输出都应该使用buf作为输出缓冲区,直到buf缓冲区被填满或者程序员直接调用fflush(译注:对于由写操作打开的文件,调用fflush将导致输出缓冲区的内容被实际地写入该文件),buf缓冲区中的内容才实际写入到stdout中。缓冲区的大小由系统头文件<stdio.h>中的BUFSIZ定义。

下面的程序的作用是把标准输入的内容复制到标准输出中,演示了setbuf库函数最显而易见的用法:

#include <stdio.h>

 

main()

{

int c;

char buf[BUFSIZ];

setbuf(stdout, buf);

    while((c=getchar())!=EOF)

        putchar(c);

}

    遗憾的是,这个程序是错误的,仅仅是因为一个细微的原因。程序中对库函数setbuf的调用,通知了输入/输出库所有字符的标准输出应该首先缓存在buf中。要找到问题出自何处,我们不妨思考一下buf缓冲区最后一次被清空是在什么时候?答案是在main函数结束之后,作为程序交回控制给操作系统之前C运行时库所必须进行的清理工作的一部分。但是,在此之前buf字符数组已经被释放!

    要避免这种类型的错误有两种办法。第一种办法是让缓冲数组成为静态数组,既可以直接显式声明buf为静态:

static char buf[BUFSIZ];

也可以把buf声明完全移到main函数之外。第二种办法是动态分配缓冲区,在程序中并不主动释放分配的缓冲区(译注:山于缓冲区是动态分配的,所以main函数结束时并不会释放该缓冲区,这样C运行时库进行清理工作时就不会发生缓冲区已释放的情况):

char *malloc();

setbuf(stdout,malloc(BUFSIZ));

    如果读者关心一些编程“小技巧”,也许会注意到这里其实并不需要检查malloc函数调用是否成功。如果malloc函数调用失败,将返回一个null指针。setbuf函数的第二个参数取值可以为null,此时标准输出不需要进行缓冲。这种情况下,程序仍然能够工作,只不过速度较慢而已。

猜你喜欢

转载自igaozh.iteye.com/blog/1680385