天天看点

printf 缓冲区原理 (你想知道的C语言 1.4)

Q: 缓冲区究竟是什么概念?

A: 缓冲: 顾名思义, 表示可以酌情商量有回旋余地. 可以想象一种只能强制遵守且说一不二的情形,一种可以协商开发此消彼长的模式.

    区: 理解成可以协助存储/处理的物理位置,一般理解成内存。

    计算机科学的"缓冲区"概念可以说是到处都有, 这种模式的作用至少有两种:

    1 协助存储和内部处理;

    2 尽可能提高潜在的效率.

        就像是数学课代表统一把作业本交到办公室要比每个学生都跑一遍办公室更高效.    

Q: printf的缓冲区究竟在哪里?

A: printf隶属libc范畴, libc属于用户态, printf的缓冲区也可以理解成用户态分配的一段内存空间. 可以想象在每次调用printf的时候,内部都会根据分配的缓冲区状态进行判断, 如果没有必要真实输出,就先暂存,否则就按要求输出.

    如下是libc FILE结构体buffer的位置:

printf 缓冲区原理 (你想知道的C语言 1.4)
printf 缓冲区原理 (你想知道的C语言 1.4)

  对于缓冲区的操作,大体就是对于一块连续的memory写操作,每次都会记录position, 当position到达缓冲区最大或者有换行标志,就会强制刷新.

  缓冲区实例: printf 内部原理和实现 (你想知道的C语言 1.2)

  Apple libc源代码:  https://opensource.apple.com/source/Libc/Libc-1272.250.1/   printf.c

Q: 既然缓冲区是用户态, 我们如何dump缓冲区的数据?

A: 我们先尝试利用通过stdout找到对应FILE数据结构buffer指针,就可以dump具体数据了.

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

void dump_stdout_buffer()
{
	FILE *fp;
	unsigned char *buf;
	int size;

	fp = stdout;
	buf = fp->_bf._base;
	size = fp->_bf._size;

	write(1, "stdout buffer:", strlen("stdout buffer:"));
	write(1, buf, size);
	write(1, "\n", 1);
}

int main(int argc, char *argv[])
{
	printf("cat");
	dump_stdout_buffer();
	printf("dog");
	dump_stdout_buffer();
	printf("\n");

	return 0;
}
           

代码: https://github.com/cxsjabc/basic/blob/dev/c/_topics/printf/dump_buffer.c

执行结果:

printf 缓冲区原理 (你想知道的C语言 1.4)

一开始, printf("cat")会把cat放入缓冲区, 后面放入dog, 最后catdog会一起输出.

当我们遇到printf没有输出的时候,可以从缓冲区角度考虑.

Q: printf缓冲区大小是多大?

A: 我们先不看libc实际设置的多少,采用江湖一直流传的手动构造缓冲区刷新的做法来反推缓冲区大小.

     https://blog.csdn.net/skyflying2012/article/details/10044035

     中心思想是不断打出一个字符, 每次间隔毫秒级别, 当第一次输出即缓冲区已经满了,此时记录输出的字符个数.

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

void delay_ms(int miliseconds)
{
	int i;
	while(miliseconds) {
		for(i = 0; i < 100000; ++i) ;
		--miliseconds;
	}
}

void check_stdout_buf_size()
{
	while(1) {
		printf("a");
		delay_ms(1);
	}
}

int main(int argc, char *argv[])
{
	check_stdout_buf_size();

	return 0;
}
           

当屏幕第一次刷出一堆a之后停住的时候,按Ctrl + C终止程序,然后记录a的个数.

 实验得到的数据是: 4096.

 我们再用代码测试4096个字符输出将发生缓冲区满.

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

void test_stdout_buf_size()
{
	int i;

	for(i = 0; i < 4096; ++i)
		printf("a");

	sleep(3);
	printf("b");
	sleep(10);
}

int main(int argc, char *argv[])
{
	test_stdout_buf_size();

	return 0;
}
           

实验结果表明: 在4096个字符a输出后,间隔13秒左右才会输出剩下的b, 即证缓冲区大小为4096!

作者:     陈曦
环境:     MacOS 10.14.5
         Apple LLVM version 10.0.1 (clang-1001.0.46.4)
         Target: x86_64-apple-darwin18.6.0
 
转载请注明出处