something with buffer 好玩儿的缓冲区:关于setbuf()一段很有意思的代码

something with buffer 有意思的缓冲区:关于setbuf()一段很有意思的代码

今天为了搞明白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

我的输入是abc。

错!

我的输入应该是‘a’'\n'‘b’'\n'‘c’'\n'

每次输入abc都伴有回车换行字符的输入!

那么在buffer中的状态应该就是这样的

something with buffer 好玩儿的缓冲区:关于setbuf()一段很有意思的代码



我们暂且不讨论那第六个格子的问题

可以很清楚的看出输入信息在buffer中的状态

这个时候是

	while((c = getchar()) != EOF)
        {
                putchar(c);
                printf("hello world!\n");
                i++;
                if(i>5)
                {
                     return 0;
                }
        }
每getchar读入一个非EOF量,这个时候就被putchar到buffer里面

然后printf等待它前面的putchar写入stdout之后,printf再将hello world写入buffer

这样

something with buffer 好玩儿的缓冲区:关于setbuf()一段很有意思的代码

jasonleaster@ubuntu:~/Desktop$ ./c.out
a
b

就会

将‘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;
                }
于是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]

可以很明显的看出,stdout并不是程序执行过程中输出的,而是return 0;程序结束之后!

接下来讨论那个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;
}

jasonleaster@ubuntu:~/Desktop$ ./d.out
a
b
c
*** stack smashing detected ***: ./d.out terminated
ahello world!


hello world!
bhello world!


hello world!
chello world!


hello world!
Aborted (core dumped)

挂的妥妥的。。。


所以关于刷新缓冲区,在C里面就两种

第一显示的调用fflush(streams);

第二程序结束


那种printf里面加个'\n'是不会刷新缓冲区的


C++里面貌似endl的时候会刷新,没玩过,不知道。

对于这个有意思的代码就讲这么多啦。。


有错漏欢迎指正

jasonleaster

 

update: 2014.03.24晚

如果用fwrite到stdout的话,在自定义的buffer情况下,应该必须强制刷新,不然不会输出到stdout

测试过了。

这里就很尴尬了,缓冲区自动刷新具体条件就很模糊了。程序结束不一定刷新缓冲区。。。