printf()将输出发送到缓冲区是什么意思?

我正在经历“C PRIMER PLUS”并且有关于“输出冲洗”的话题。 现在它说:

printf()语句将输出发送到称为缓冲区的中间存储。 不时地,缓冲区中的材料被发送到屏幕。 输出从缓冲区发送到屏幕的标准C规则是明确的:

  1. 它在缓冲区满时发送。
  2. 遇到换行符时。
  3. 当有即将到来的输入时。

(将缓冲区的输出发送到屏幕或文件称为刷新缓冲区。)

现在,validation以上陈述。 我写了这个简单的程序:

 #include int main(int argc, char** argv) { printf("Hello World"); return 0; } 

所以,printf()都不包含新行,也没有一些即将发生的输入(例如scanf()语句或任何其他输入语句)。 那么为什么它会在输出屏幕上打印内容。

我们假设第一个条件被validation为真。 缓冲区已满(根本不会发生)。 记住这一点,我将printf()中的语句截断为

 printf("Hi"); 

它仍然在控制台上打印语句。

所以这里的交易是什么,所有上述条件都是假的,但我仍然在屏幕上得到输出。 你能详细说明吗? 看来我在理解这个概念时犯了一个错误。 任何帮助都非常感谢。

编辑:正如一个非常有用的注释所示,可能在程序结束后执行exit()函数导致所有缓冲区刷新,从而导致控制台上的输出。 但是如果我们在执行exit()之前按住屏幕。 像这样,

 #include int main(int argc, char** argv) { printf("Hello World!"); getchar(); return 0; } 

它仍然在控制台上输出。

输出缓冲是一种优化技术。 将数据写入某些设备(硬盘fe)是一项昂贵的操作; 这就是缓冲出现的原因。 从本质上讲,它避免了逐字节(或char-by-char)写入数据并将其收集在缓冲区中,以便一次写入几个KiB数据。

作为优化,输出缓冲必须对用户透明(即使对程序也是透明的)。 它不得影响程序的行为; 无论是否缓冲(或使用不同大小的缓冲区),程序必须表现相同。 这就是你提到的规则。

缓冲区只是存储器中的一个区域,临时存储要写入的数据,直到有足够的数据累积,以使设备的实际写入过程高效。 某些设备(硬盘等)甚至不允许以小块写入(或读取)数据,而只允许以某些固定大小的块进行写入(或读取)数据。

缓冲区刷新规则:

  1. 它在缓冲区满时发送。

这很明显。 缓冲区已满,其目的已实现,让我们将数据推送到设备。 此外,可能有更多的数据来自该计划,我们需要为它腾出空间。

  1. 遇到换行符时。

有两种类型的设备:线路模式和块模式。 此规则仅适用于线路模式设备(例如,终端)。 写入磁盘时刷新换行符上的缓冲区没有多大意义。 但是当程序写入终端时,这样做很有意义。 在终端前面,用户不耐烦地等待输出。 不要让他们等太多。

但为什么输出到终端需要缓冲? 在终端上写字并不昂贵。 这是正确的,当终端物理上位于处理器附近时。 当终端和处理器分开一半并且用户通过远程连接运行程序时也不是这样。

  1. 当有即将到来的输入时。

它应显示为“当同一设备上存在阻止输入时”以使其清楚。

阅读也被缓冲,原因与写作:效率相同。 读取代码使用自己的缓冲区。 它在需要时填充缓冲区,然后scanf()和其他输入读取函数从输入缓冲区获取它们的数据。

当输入即将在同一设备上发生时,必须刷新缓冲区(实际写入设备的数据)以确保一致性。 程序已将一些数据发送到输出,现在它希望读回相同的数据; 这就是为什么必须将数据刷新到设备以便读取代码在那里找到并加载它的原因。

但是为什么在应用程序退出时刷新缓冲区?

错误…缓冲是透明的,它不得影响应用程序行为。 您的应用程序已将一些数据发送到输出。 当应用程序退出时,数据必须在(在输出设备上)。

出于同样的原因,关闭相关文件时也会刷新缓冲区。 这就是应用程序退出时发生的情况:清理代码关闭所有打开的文件(标准输入和输出只是从应用程序角度来看的文件),关闭强制刷新缓冲区。

C标准中的exit()规范的一部分(给定POSIX链接)是:

接下来,刷新所有带有未写入缓冲数据的开放流,关闭所有打开的流,…

因此,当程序退出时,将刷新挂起的输出,而不管换行符等。同样,当文件关闭( fclose() )时,将写入挂起的输出:

流的任何未写入的缓冲数据都被传送到主机环境以写入文件; 任何未读缓冲的数据都将被丢弃。

当然, fflush()函数会刷新输出。

问题中引用的规则并不完全准确。

  1. 缓冲区已满 – 这是正确的。

  2. 遇到换行符时 – 虽然经常适用,但这不正确。 如果输出设备是“交互设备”,则默认为线路缓冲。 但是,如果输出设备是“非交互式”(磁盘文件,管道等),则输出不一定(或通常)是行缓冲的。

  3. 当有即将到来的输入时 – 这也是不正确的,尽管它通常是它的工作方式。 同样,这取决于输入和输出设备是否“交互式”。

可以通过调用setvbuf()来修改输出缓冲模式,以设置无缓冲,行缓冲或完全缓冲。

标准说(§7.21.3):

¶3当流未缓冲时 ,字符应尽快从源或目的地出现。 否则,可以将字符作为块累积并发送到主机环境或从主机环境发送。 当流被完全缓冲时 ,当填充缓冲区时,字符旨在作为块传输到主机环境或从主机环境传输。 当流被行缓冲时 ,当遇到换行符时 ,字符将作为块传输到主机环境或从主机环境传输。 此外,当填充缓冲区,在无缓冲流上请求输入时,或者在需要从主机环境传输字符的行缓冲流上请求输入时,字符旨在作为块传输到主机环境。 。 对这些特性的支持是实现定义的,可能会受到setbufsetvbuf函数的影响。

¶7在程序启动时,预定义了三个文本流,无需明确打开 – 标准输入 (用于读取常规输入), 标准输出 (用于写入常规输出) 和标准错误 (用于写入诊断输出)。 最初打开时,标准错误流未完全缓冲; 当且仅当可以确定流不参考交互设备时,标准输入和标准输出流是完全缓冲的。

另外,§5.1.2.3 程序执行说:

  • 交互设备的输入和输出动态应按照7.21.3的规定进行。 这些要求的目的是尽快出现无缓冲或行缓冲输出,以确保在程序等待输入之前实际出现提示消息。

printf的奇怪行为,缓冲可以用下面简单的C代码来解释。 请仔细阅读整个事情执行并理解,因为下面的内容并不明显(有点棘手)

 #include  int main() { int a=0,b=0,c=0; printf ("Enter two numbers"); while (1) { sleep (1000); } scanf("%d%d",&b,&c); a=b+c; printf("The sum is %d",a); return 1; } 

实验#1:

操作:编译并运行上面的代码

观察:

预期的产出是

 Enter two numbers 

但是没有看到这个输出

实验#2:

操作:在循环时移动Scanf语句。

 #include  int main() { int a=0,b=0,c=0; printf ("Enter two numbers"); scanf("%d%d",&b,&c); while (1) { sleep (1000); } a=b+c; printf("The sum is %d",a); return 1; } 

观察:现在打印输出(原因在下面)(仅通过scanf位置变化)

实验#3:

操作:现在将\ n添加到print语句,如下所示

 #include  int main() { int a=0,b=0,c=0; printf ("Enter two numbers\n"); while (1) { sleep (1000); } scanf("%d%d",&b,&c); a=b+c; printf("The sum is %d",a); return 1; } 

观察:输出Enter two numbers (添加\ n后)

实验#4:

操作:现在从printf行中删除\ n,注释while while循环,scanf行,添加行,printf行以打印结果

 #include  int main() { int a=0,b=0,c=0; printf ("Enter two numbers"); // while (1) // { // sleep (1000); // } // scanf("%d%d",&b,&c); // a=b+c; // printf("The sum is %d",a); return 1; } 

观察:在屏幕上打印“输入两个数字”行。

回答:

理查德史蒂文斯的书中描述了这种奇怪行为背后的原因。

PRINTF PRINTS TO SCREEN WHEN

printf的工作是将输出写入stdout缓冲区。 内核刷新输出缓冲区时

  1. 内核需要从输入缓冲区读取内容。 (实验#2)
  2. 当它遇到换行符时(因为stdout默认设置为linebuffered)(实验#3)
  3. 程序退出后(刷新所有输出缓冲区)(实验#4)

默认情况下,stdout设置为行缓冲,因此printf将不会打印,因为该行未结束。 如果它没有缓冲,则所有行都按原样输出。 然后全缓冲,只有当缓冲区已满时,才会刷新。