你真的理解printf函数吗? ##C语言

来看下面一个程序:

#include 
#include 

int main() {

printf("Hello");
sleep(100);

return 0;
}

上面这个程序会在执行sleep之前输出hello吗?

既然我这样问了,那么答案肯定没有那么简单,答案是不会输出。

为什么呢?接下来我们就来分析一下:

printf函数的缓冲区

事实上,printf函数有一个缓冲区,叫做stdout,那么什么是缓冲区呢?我们该怎么理解这个概念呢?

根据我自己的理解,我们可以把printf的缓冲区看作一个先进先出的队列。而printf函数要做的就是将要输出的内容从左向右依次放进缓冲区,例如:

#include 

int main() {

printf("Hello");
return 0;

}

当我们执行上面的程序到return之前时,printf已经将这个“Hello”放入了一个队列:

H e l l o

 此时,printf函数的功能已经执行结束了。

此时你可能满脑子问号,是不是少了个输出的功能?慢慢听我解释。

linux中stdout输出的条件

printf函数本身并不具有输出的功能,真正实现输出的还是一个名为write或者writev的系统调用,我们这里不讨论这一点,我们将注意力放在printf对write这个系统调用的优化上,这个优化就是这个名为缓冲区的区域。

为什么要添加一个缓冲区?这不是偶然,当然是有原因的。

因为缓冲区的概念较为透明,我们大多数情况下都会用到他但不会感觉到他的存在,因此我举个例子,相信你很快就能理解:

假设,我们想让系统输出一串字符串:

abc

 我们会这样写:

printf("abc");

 正常情况下,我们在uinx/linux系统的terminal中看到的结果是这样的:

abc%

 这个%是因为没有换行,类似于vscode中的光标。

那么现在来假设没有缓冲区,我们会看到什么样的输出呢?

abc%

 是的,你没有看错,结果没有改变,但是本质上发生了很大的变化。

在第一个输出中,系统调用只被调用了一次,也就是write,然而在第二个假设的输出情况下,系统调用被调用了三次。

你可能还没有明白,我们来举一个更加明显的例子,要知道,不仅仅是printf有缓冲区,scanf函数也有(名字叫做stdin),我们现在假设想做一个让系统输出我们想要指定的内容,我们会这样写:

#include 

#define MAX_N 10000

int main() {
char str[MAX_N];
scanf("%s", str);
printf("%s", str);
return 0;
}

 正常情况下,scanf会读取你输入的内容,但是当没有输入和输出缓冲时,情况会变得糟糕:

假设我想让他输出:

abc

那么我会先想要去按下abc让scanf函数去接收。但事实情况是这样的:

当你按下a的一瞬间,系统屏幕上出现了下面的情况:

a

a

%

程序结束。

为什么?因为没有缓冲区,当没有缓冲区的时候,系统没有地方去保存你输入的信息,会变成一个“秒男”,只能输出你的信息了,然后就会结束。

事实上,上面的例子不是很严谨,因为printf和scanf函数的缓冲区都是不可缺少的,因此用来做假设会出现很多不可预测的情况。

我的目的是想要告诉你,缓冲区的作用是减少系统调用的次数,调高系统运行的效率和速度。

回归正题,printf的缓冲区似乎并不会因为执行了printf函数而输出内容,那真正让计算机去输出的条件是什么呢?

有以下几种情况:

  1. 缓冲区满
  2. 缓冲区遇到换行符,按照行缓冲方式输出
  3. 缓冲区遇到文件结束符EOF
  4. 缓冲区遇到强制刷新的命令,例如fflush或setbuf函数
  5. 缓冲区所属的文件或流被关闭,例如fclose函数或exit函数

以上就是系统输出缓冲区内容的所有条件。

回到最一开始的例子,prinf函数按照从左到右的顺序依次将字符放入缓冲区,然后进入阻塞态(sleep),这个时候并没有触发上面的任何一个条件,因此系统并不会输出,如果你多等一会,等到sleep执行结束后,会发现系统又输出了,这是因为遇到了条件5。进程被关闭,缓冲区所属的文件自然就会被关闭。

你真的理解printf函数吗? ##C语言_第1张图片

你可能感兴趣的:(c语言,c++)