01
—
C标准库缓冲区探索
在计算机里缓存是一个很重要的概念,C标准库里大量使用了缓存,最为典型的就是标准输入和标准输出的缓存,关于C语言的输入和输出看这篇文章即可,利用好缓存可以大幅提高程序性能,首先我们看一下下面这段代码会输出什么?
#include 《stdio.h》 #include 《unistd.h》 int main() { printf(“Hello World!”); //往标准输出输出字符串 //程序停留在while循环里,程序退出会强制刷新缓冲区数据 while(1){ sleep(1); } return 0; }
我们在程序里调用printf函数打算在标准输出“Hello World!”,下面的while(1)循环是想让程序停在这里不退出程序,每次睡眠1s避免占用大量CPU资源,在Linux中包含unistd.h头文件才能使用sleep函数。现在我们编译以下看看会输出什么?
我们看到,其实什么都没有输出。但是从程序上看,我们已经调用了printf函数往标准输出输出字符串,这就是缓存在起作用了。printf函数默认是行缓冲,当输出字符串里有 或者行缓冲区被填满或者手动调用fflush函数才会一次性将数据输出。现在你只要加上一条语句输出换行符,就能在标准输出输出字符串了。
printf(“ ”); //换行,默认标准输出会立即输出刷新缓冲区
或者我们手动调用fflush也可以强制刷新缓冲区,输出字符串。
fflush(stdout); //强制刷新标准输出缓冲区
往标准错误输出字符串的语句编译运行后会发生什么呢?
fprintf(stderr, “error information”); //往标准错误输出输出信息
fprintf函数将信息往第一个FILE指针类型参数输出,这里第一个参数我们传入stderr,编译运行后立即在控制台上输出字符串“error information”。标准错误输出和标准输出运行测试结果对比我们知道,调用fprintf函数往标准错误输出信息时不需要加字符‘ ’,也不需要强制刷新缓冲区也能立即输出信息。这是因为标准错误输出是无缓冲模式,写入什么数据就立即输出什么数据。
下面我们再看看输入代码
#include 《stdio.h》 int main() { char arr[100] = {0}; scanf(“%s”, arr); return 0; }
在这段代码里,程序运行后我们从标准输入输入数据,直到按下回车才将数据输入到数组arr里。在按下回车后,实际上刷新了输入缓冲区将数据一次性写入到数组arr里。
03
—
缓冲区的作用
在计算机里应用程序调用一个系统调用从用户态进去内核态再将结果回到用户态开销较大。如果我们调用printf函数,每次输出一个字符都要从用户态切换到内核态,那么连续输出多个字符开销成本将会非常大,这个时候缓存就起到非常大的作用了,输出的字符串先在应用程序里缓存起来,缓存到一定数量后再调用系统调用一次性将缓存数据输出到标准输出。
由于只调用了一次系统调用,比连续调用多个系统调用性能高上不少。在生活中我们也能感受到缓存带来的效率提升,打个比方你办公室有一个垃圾桶,楼下有倒垃圾的地点,如果扔一个垃圾到垃圾桶里我们就拿去倒掉,将会在办公室和楼下之间来回很多趟,浪费大量时间。如果将垃圾桶装满,再一次性拿到楼下倒掉,只需要跑一次就能把垃圾全都倒掉,节省了时间,提高了效率。
04
—
缓冲模式和使用方式
C语言里有行缓冲模式、全缓冲模式和无缓冲模式。
行缓冲模式:填满缓冲区或者有换行符‘ ’或者调用fflush函数强制刷新缓冲区会立即输出。
全缓冲模式:填满缓冲区或者调用fflush函数强制刷新缓冲区会立即输出。
无缓冲模式:写入什么数据就会立即输出什么数据,例如标准错误输出默认的缓冲模式。
下面我们用实际代码演示如何使用三种缓冲模式,设置缓冲模式会用到setvbuf函数,我们先来看看setvbuf函数声明。
/* Make STREAM use buffering mode MODE. If BUF is not NULL, use N bytes of it for buffering; else allocate an internal buffer N bytes long. */ extern int setvbuf (FILE *__restrict __stream, char *__restrict __buf, int __modes, size_t __n) __THROW;
第一个参数是FILE类型指针,第二个参数是外部缓冲区指针,第三个参数是缓冲模式,第四个参数是缓冲大小,如果不使用外部缓冲区,函数内部将会调用malloc申请一块内存作为内部缓冲区。
形参mode提供了三个参数分别设置不同的缓冲区模式
_IONBF unbuffered _IOLBF line buffered _IOFBF fully buffered
无缓冲模式实例代码
#include 《stdio.h》 #include 《unistd.h》 int main() { setvbuf(stdout, NULL, _IONBF, 0); //标准输出设置为无缓冲,不使用外部缓冲区 printf(“Hello World!”); return 0; }
编译运行会立即输出
Hello World!
行缓冲模式实例代码
#include 《stdio.h》 #include 《unistd.h》 int main() { setvbuf(stdout, NULL, _IOLBF, 0); //标准输出设置为行缓冲模式,不使用外部缓冲区 printf(“how are you”); //不会立即输出字符串 fflush(stdout); //强制刷新缓冲区,立即输出字符串 return 0; }
编译运行后,由于调用了fflush会强制刷新数据到标准输出。
全缓冲模式实例代码
#include 《stdio.h》 #include 《unistd.h》 int main() { setvbuf(stdout, NULL, _IOFBF, 0); //标准输出设置为全缓冲模式,不使用外部缓冲区 printf(“Hello World!”); //不会立即输出 printf(“how are you”); //不会立即输出 printf(“ ”); while(1){ sleep(1); } return 0; }
编译运行后发现没有任何输出,现在我们在while循环前面加上下面这条语句,编译运行看看。
fflush(stdout); //强制刷新缓冲区
编译运行后立即输出了字符串!
同样的使用方式可以用于标准输入和标准错误输出,只需要把stdout缓存stdin或者stderr即可。
编辑:jq
全部0条评论
快来发表一下你的评论吧 !