今天为了搞明白buffer的机制弄了很久。。。以至于本来该做的事情都耽误了
在CSDN别人blog上面看到这么一段代码,很有意思
#include<stdio.h> #include<stdlib.h> int main() { int c = 0; int i = 0; setbuf(stdout,malloc(5)); while((c = getchar()) != EOF) { putchar(c); #if 1 printf("hello world!\n"); i++; if(i>5) { return 0; } #endif } return 0; }
测试结果:
jasonleaster@ubuntu:~/Desktop$ ./c.out
a
b
c
ahello world!
hello world!
bhello world!
hello world!
chello world!
hello world!
首先,那个预处理#if 1 #endif 在预处理阶段,1为真,于是if 和endif之间的内容被编译
代码就简化成下面这个样子了
#include<stdio.h> #include<stdlib.h> int main() { int c = 0; int i = 0; setbuf(stdout,malloc(5)); while((c = getchar()) != EOF) { putchar(c); printf("hello world!\n"); i++; if(i>5) { return 0; } } return 0; }
void setbuf(FILE *restrict fp ,char *restrict buf );
setbuf是把文件流fp的buffer设置为buf
void *malloc(size_t size);
malloc是向堆申请一个size大小的连续内存,注意!是连续的!这非常关键
malloc返回值是一个空指针,指向申请的这块大小为size字节的区域
setbuf(stdout,malloc(5));
于是这里就把stdout的buffer设置为了malloc申请来的5个字节大小的内存区域。
while((c = getchar()) != EOF) { putchar(c); printf("hello world!\n"); i++; if(i>5) { return 0; } }
从标准输入读取字符,然后赋值给c,只要不是ctrl+D产生的EOF,一切OK。
然后putchar打印这个字符c
注意,这里getchar,putchar都是标准库函数,所以他们都是使用标准输入输出流的
这里putchar将变量c对应的ascii字符输出到标准输出流
而这个过程中是要经过buffer的
jasonleaster@ubuntu:~/Desktop$ ./c.out
a
b
c
我的输入是abc。
错!
我的输入应该是‘a’'\n'‘b’'\n'‘c’'\n'
每次输入abc都伴有回车换行字符的输入!
那么在buffer中的状态应该就是这样的
我们暂且不讨论那第六个格子的问题
可以很清楚的看出输入信息在buffer中的状态
这个时候是
while((c = getchar()) != EOF) { putchar(c); printf("hello world!\n"); i++; if(i>5) { return 0; } }
然后printf等待它前面的putchar写入stdout之后,printf再将hello world写入buffer
这样
jasonleaster@ubuntu:~/Desktop$ ./c.out
a
b
c
就会
将‘a’写入buffer然后将printf要打印的字符写入buffer,i++,
然后'\n'写入buffer,然后将printf要打印的字符写入buffer,i++,
将‘b’写入buffer然后将printf要打印的字符写入buffer,i++,
然后'\n'写入buffer,然后将printf要打印的字符写入buffer,i++,
将‘c’写入buffer然后将printf要打印的字符写入buffer,i++,
然后'\n'写入buffer,然后将printf要打印的字符写入buffer,i++,
此时i == 6
if(i>5) { return 0; }
这个时候才有输出!此时刷新stdout的buffer。输出到stdout
(gdb) list 1 #include<stdio.h> 2 #include<stdlib.h> 3 4 int main() 5 { 6 int c = 0; 7 int i = 0; 8 //char buffer[10]; 9 setbuf(stdout,malloc(5)); 10 while((c = getchar()) != EOF) (gdb) b 10 Breakpoint 1 at 0x4006d1: file ./test.c, line 10. (gdb) run Starting program: /home/liuzjian/Desktop/d.out Breakpoint 1, main () at ./test.c:10 10 while((c = getchar()) != EOF) (gdb) step a 12 putchar(c); (gdb) step 14 printf("hello world!\n"); (gdb) 15 i++; (gdb) 16 if(i>5) (gdb) 10 while((c = getchar()) != EOF) (gdb) 12 putchar(c); (gdb) 14 printf("hello world!\n"); (gdb) 15 i++; (gdb) 16 if(i>5) (gdb) 10 while((c = getchar()) != EOF) (gdb) b 12 putchar(c); (gdb) 14 printf("hello world!\n"); (gdb) 15 i++; (gdb) 16 if(i>5) (gdb) 10 while((c = getchar()) != EOF) (gdb) 12 putchar(c); (gdb) 14 printf("hello world!\n"); (gdb) 15 i++; (gdb) 16 if(i>5) (gdb) 10 while((c = getchar()) != EOF) (gdb) c 12 putchar(c); (gdb) 14 printf("hello world!\n"); (gdb) 15 i++; (gdb) 16 if(i>5) (gdb) 10 while((c = getchar()) != EOF) (gdb) 12 putchar(c); (gdb) 14 printf("hello world!\n"); (gdb) 15 i++; (gdb) 16 if(i>5) (gdb) 18 return 0; (gdb) 23 } (gdb) 0x00007ffff7a33ea5 in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6 (gdb) Single stepping until exit from function __libc_start_main, which has no line number information. ahello world! hello world! bhello world! hello world! chello world! hello world! [Inferior 1 (process 9873) exited normally]
接下来讨论那个malloc(5)
这是个。。。很糟糕的做法
但是我还是讨论问题吧。。。不吐槽了
这里malloc了5个byte当buffer
其实这里越界了,但是由于malloc是在堆上面申请的内存,这样是连续的,导致程序看起来没问题
如果把那个malloc的5个byte换成数组就有问题了
#include<stdio.h> #include<stdlib.h> int main() { int c = 0; int i = 0; char buffer[10]; setbuf(stdout,buffer); while((c = getchar()) != EOF) { putchar(c); #if 1 printf("hello world!\n"); i++; if(i>5) { return 0; } #endif } return 0; }
挂的妥妥的。。。
所以关于刷新缓冲区,在C里面就两种
第一显示的调用fflush(streams);
第二程序结束
那种printf里面加个'\n'是不会刷新缓冲区的
C++里面貌似endl的时候会刷新,没玩过,不知道。
对于这个有意思的代码就讲这么多啦。。
有错漏欢迎指正
jasonleaster
update: 2014.03.24晚
如果用fwrite到stdout的话,在自定义的buffer情况下,应该必须强制刷新,不然不会输出到stdout
测试过了。
这里就很尴尬了,缓冲区自动刷新具体条件就很模糊了。程序结束不一定刷新缓冲区。。。