标准I/O的缓冲机制的实例讲解

标准I/O库对每个I/O流自动进行缓冲,从而避免了应用程序为了减少read和write系统调用而设置合适的缓冲区长度。
标准I/O提供了三种类型的缓冲


我们下来看一个《unix环境高级编程》上的一个输出标准输入,标准输出,标准出错和普通文件的缓冲信息的例子
  4 void pr_stdio(char *,FILE *);
  5 
  6 int main(void){
  7         FILE *fp;
  8         fputs("entry any character\n",stdout);
  9         if(getchar()==EOF){
 10                 printf("getchar error\n");
 11                 exit(1);
 12         }
 13         fputs("one line to standard error\n",stderr);
 14 
 15         pr_stdio("stdin",stdin);
 16         pr_stdio("stdout",stdout);
 17         pr_stdio("stderr",stderr);
 18 
 19         if((fp=fopen("test","r+"))==NULL){
 20                 perror("fopen error");
 21                 exit(1);
 22         }
 23         if(getc(fp)==EOF){
 24                 printf("getc error\n");
 25                 exit(1);
 26         }
 27         pr_stdio("test",fp);
 28 
 29         exit(0);
 30 }
 31 
 32 void pr_stdio(char *name,FILE *fp){
 33         printf("stream=%s ",name);
 34 
 35         if(fp->_IO_file_flags & _IO_UNBUFFERED)
 36                 printf("unbuffered");
 37         else if(fp->_IO_file_flags & _IO_LINE_BUF)
 38                 printf("line buffered");
 39         else
 40                 printf("fully buffered");
 41         printf(", buf size=%d\n",fp->_IO_buf_end-fp->_IO_buf_base);
 42 }


我们直接看输出:
feng@ubuntu:~/learn_linux_c_second/chapter_5$ ./a.out
entry any character


one line to standard error
stream=stdin line buffered, buf size=1024
stream=stdout line buffered, buf size=1024
stream=stderr unbuffered, buf size=1
stream=test fully buffered, buf size=4096


feng@ubuntu:~/learn_linux_c_second/chapter_5$ ./a.out <test >std.out 2>std.err   //标准输入,输出重定向到普通文件
feng@ubuntu:~/learn_linux_c_second/chapter_5$ cat std.err
one line to standard error
feng@ubuntu:~/learn_linux_c_second/chapter_5$ cat std.out
entry any character
stream=stdin fully buffered, buf size=4096
stream=stdout fully buffered, buf size=4096
stream=stderr unbuffered, buf size=1
stream=test fully buffered, buf size=4096
feng@ubuntu:~/learn_linux_c_second/chapter_5$ 




针对上面的代码,我们需要做一下说明,在打印缓冲信息之前我们对每个i/o流都执行了i/o操作,是因为第一个i/o操作会造成该流的缓冲分配,如果
不执行一次i/o操作,我们只能从知道缓冲类型,而不得到缓冲大小。上面的test为一个普通文件


从输出中我们又以下结论:
系统默认情况是标准输入,输出 连接至终端时,他们是行缓冲的,缓冲大小为1024.当这两个留被重定向到普通文件是他们是全缓冲的,缓冲大小为4096
标准出错一直都是非缓冲
普通文件默认是全缓冲。
下面我们就来一一解释这些街结论。




1全缓冲:
这种情况下在填满标准I/O缓冲区后才进行实际的I/O操作。对于驻留在磁盘上的文件通常是由标准I/O实施全缓冲的。重向到普通文件的标准输入和输出也是全缓冲的
什么叫填满标准I/O缓冲区呢,就是说,标准I/O维护一个适当大小的缓冲区,当我们调用fwrite这类的标准I/O函数时,他们总是先写到内存中的标准I/O维护的 缓冲区,当这个缓冲区被填满或调用fflush冲洗缓冲区时才实际调用write系统调用去写磁盘(当然这里即使是实际i/o操作也不一定真的写到磁盘了,因为计算机内都有高速缓存)


我们来直接看测试程序吧:
  1 #include<stdlib.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 
  5 #define COUNT   500
  6 
  7 int main(void){
  8         FILE *fp=fopen("test","w+");
  9         if(fp==NULL){
 10                 perror("fopen error");
 11                 exit(1);
 12         }       
 13         
 14         int i;
 15         for(i=0;i<COUNT;i++){
 16                 if(fwrite("c",sizeof(char),1,fp)!=1){
 17                         printf("write error\n");
 18                         exit(1);
 19                 }       
 20         }       
 21 //      fflush(fp);
 22         int fd=fileno(fp);
 23         lseek(fd,0,SEEK_SET);
 24         char buf[2];
 25         int nreads;
 26         printf("\n %c  %c\n",buf[0],buf[1]);
 27         nreads=read(fd,buf,2);
 28         if(nreads==-1){
 29                 perror("read error");
 30                 exit(0);
 31         }
 32         printf("\n %c  %c\n",buf[0],buf[1]);
 33         
 34         exit(0);
 35 }


分析:我们用标准I/O函数写了500个字幕到普通文件test中,(前面我们已经知道普通文件默认是全缓冲的大小为4096),然后我们用read系统调用去读文件内容
。因为我们只写了500个字符,那么标准i/o缓冲区未被写满,我们也没有调用fflush,那么我们可以猜想,我们是读不到任何数据的
我们看下输出:
feng@ubuntu:~/learn_linux_c_second/chapter_5$ ./a.out


 p  ?
read 0 characters


 p  ?
这里证明我们的猜想是正确的。这里有输出是应为我们并未初始化数组,所以输出的内容是该内存位置之前存在的数据。我们未初始化的原因也是用两个输出来前后对比
 
 这里需要强调一点的是:我们的读操作 不能调用标准I/O的fread,因为我们的数据是写在缓冲区的,这是缓冲区有数据,那么fread就会从缓冲区读,就会读出数据
 所以我们用fileno(fp),得到该流对应的底层文件描述符,然后用该描述符直接去读文件内容。
 
 注意21行。如果我们执行这句 fflush会怎么样呢。我们测试下:
 feng@ubuntu:~/learn_linux_c_second/chapter_5$ ./a.out


 {  ?
read 2 characters


 c  c
情况如我们上面说的 ,调用fflush会强制冲洗缓冲区的内容,就会导致调用系统write去写文件,那么我们就能从文件中读到数据了


现在 我们在把第五行的 COUNT 宏定义改成5000(>4096),看下结果会怎么样。注意我们不执行fflush。因为我们要测试缓冲区满会不会造成冲洗,所以要排除fflush强制冲洗的情况
feng@ubuntu:~/learn_linux_c_second/chapter_5$ ./a.out


 |  ?
read 2 characters


 c  c
结果也不出所料。由于我们想缓冲区写入5000个数据,造成缓冲区满了,所以被冲洗,执行了实际i/o操作,所以我们也就能从文件中读到数据了


2行缓冲:
在这种情况下,默认标准输入输出中遇到换行符时,标准i/o执行实际i/o操作。
直接例子:
  
  5 int main(void){
  6 
  7         int i;
  8         for(i=0;i<COUNT;i++){
  9                 if(fwrite("a",sizeof(char),1,stdout)!=1){
 10                         printf("fwrite error\n");
 11                         exit(1);
 12                 }
 13         }
 14         printf("\n");
 15         for(i=0;i<COUNT;i++){
 16                 if(fwrite("b",sizeof(char),1,stdout)!=1){
 17                         printf("fwrite error");
 18                         exit(1);
 19                 }
 20         }
 21 //fflush(stdout);
 22         while(1);
 23         exit(0);
 24 
 25 }
~        
输出:
feng@ubuntu:~/learn_linux_c_second/chapter_5$ ./a.out                                                
aaaaa


                           
我们会发下程序一直在这里循环但是只输出了a 但是b并未输出。因为标准i/o默认是链接到终端的是行缓冲(上面得到大小为1024),他们遇到 换行符,或缓冲区满,或调用fflush时才会导致冲洗缓冲区,所以我们写入5个 a 后打印了一个换行符,于是缓冲区被冲洗,a被输出,但是我们
之后只写了5个b 缓冲区未满,我们也没用调用fflush,所以b不会输出。


如果我们在 写完5个 b 后调用一个fflush可以猜想b也是会输出的,我们测试一下
feng@ubuntu:~/learn_linux_c_second/chapter_5$ ./a.out
aaaaa
bbbbb


的确如此,程序还是停在循环处,但这时候b 也被输出。


同样的如果我们写 大于1024个b,即使不调用fflush,b还是会被输出。这里我们就不测试了。总不能搞的屏幕全是字母b吧 


需要注意的一点就是 当标准输入输出重定向到普通文件时,就会变成全缓冲(最开始的测试结果已经说明了这个特性)


3非缓冲:
这个就好理解了,就是说标准i/o不会对其进行缓冲:
做个测试:


  4 int main(void){
  5         int i;
  6         for(i=0;i<5;i++){
  7                 if(fwrite("a",sizeof(char),1,stderr)!=1){
  8                         printf("fwrite error");
  9                         exit(1);
 10                 }
 11         }
 12         while(1);
 13 }
输出:
feng@ubuntu:~/learn_linux_c_second/chapter_5$ ./a.out
aaaaa


我们没有做任何处理,但是标准出错还是输出了a。这就说明对于标准出错是非缓冲的。即使是被重定向,标准输出还是非缓冲的(最开始的那个理解的结果已经说明)
这也容易理解。任何时候只要有错误,我们都需要立刻知道。所以他是非缓冲的

你可能感兴趣的:(标准I/O的缓冲机制的实例讲解)