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