我正在经历" C PRIMER PLUS"并且有关于" OUTPUT FLUSHING"的这个话题。 现在它说:
printf()
语句将输出发送到称为缓冲区的中间存储。 不时地,缓冲区中的材料被发送到屏幕。该 输出从缓冲区发送到屏幕的标准C规则是 明确:
- 缓冲区已满时发送。
- 遇到换行符时。
- 当有即将到来的输入时。
醇>(将缓冲区的输出发送到屏幕或文件称为刷新 缓冲区。)
现在,要验证以上陈述。我写了这个简单的程序:
#include<stdio.h>
int main(int argc, char** argv) {
printf("Hello World");
return 0;
}
所以,printf()都不包含新行,也没有一些即将发生的输入(例如scanf()语句或任何其他输入语句)。那么为什么它会在输出屏幕上打印内容。
假设第一个条件被验证为真。缓冲区已满(这根本不会发生)。 记住这一点,我将printf()中的语句截断为
printf("Hi");
仍然在控制台上打印语句。
所以这里的交易是什么,所有上述条件都是假的,但我仍然在屏幕上获得输出。 你能详细说明吗?看来我在理解这个概念时犯了一个错误。任何帮助都非常感谢。
编辑:正如一篇非常有用的评论所暗示的那样,可能在程序结束后执行exit()函数会导致所有缓冲区刷新,从而导致控制台上的输出。但是如果我们在执行exit()之前保持屏幕。像这样,#include<stdio.h>
int main(int argc, char** argv) {
printf("Hello World!");
getchar();
return 0;
}
它仍在控制台上输出。
答案 0 :(得分:4)
C标准(给定POSIX链接)中exit()
的部分规范是:
接下来,刷新所有带有未写入缓冲数据的开放流,关闭所有打开的流,...
因此,当程序退出时,将刷新挂起的输出,而不管换行符等。同样,当文件关闭(fclose()
)时,会写入挂起的输出:
流的任何未写入的缓冲数据都将传递到主机环境以写入文件;任何未读缓冲的数据都将被丢弃。
当然,fflush()
函数会刷新输出。
问题中引用的规则并不完全准确。
当缓冲区已满时 - 这是正确的。
遇到换行符时 - 虽然经常适用,但这不正确。如果输出设备是“交互设备”,则默认为线路缓冲。但是,如果输出设备是“非交互式”(磁盘文件,管道等),则输出不一定(或通常)是行缓冲的。
当有即将到来的输入时 - 这也是不正确的,尽管它通常是它的工作方式。同样,这取决于输入和输出设备是否是“交互式”。
可以通过调用setvbuf()
来修改输出缓冲模式
不设置缓冲,线路缓冲或完全缓冲。
标准说(§7.21.3):
¶3当流 unbuffered 时,字符应尽快从源或目的地出现。否则,可以将字符作为块累积并发送到主机环境或从主机环境发送。当流完全缓冲时,当填充缓冲区时,字符将作为块传输到主机环境或从主机环境传输。当流行缓冲时,当遇到换行符时,字符将作为块传输到主机环境或从主机环境传输。此外,当填充缓冲区,在无缓冲流上请求输入时,或者在需要从主机环境传输字符的行缓冲流上请求输入时,字符旨在作为块传输到主机环境。 。对这些特性的支持是实现定义的,可能会受到
setbuf
和setvbuf
函数的影响。...
¶7在程序启动时,预定义了三个文本流,无需明确打开 - 标准输入(用于读取常规输入),标准输出(用于编写常规文本)输出),和标准错误(用于写入诊断输出)。最初打开时,标准错误流未完全缓冲;当且仅当可以确定流不参考交互设备时,标准输入和标准输出流是完全缓冲的。
此外,§5.1.2.3程序执行说:
- 交互设备的输入和输出动态应按照7.21.3的规定进行。这些要求的目的是尽快显示无缓冲或行缓冲输出,以确保在程序等待输入之前实际显示提示消息。
答案 1 :(得分:3)
输出缓冲是一种优化技术。将数据写入某些设备(硬盘f.e.)是一项昂贵的操作;这就是缓冲出现的原因。从本质上讲,它避免了逐字节(或char-by-char)写入数据并将其收集在缓冲区中,以便一次写入几个KiB数据。
作为优化,输出缓冲必须对用户透明(即使对程序也是透明的)。它不得影响程序的行为;无论是否缓冲(或使用不同大小的缓冲区),程序必须表现相同。这就是你提到的规则。
缓冲区只是内存中的一个区域,临时存储要写入的数据,直到有足够的数据累积,以使设备的实际写入过程高效。某些设备(硬盘等)甚至不允许以小块写入(或读取)数据,而只允许以某些固定大小的块进行写入。
缓冲区刷新规则:
- 缓冲区已满时发送。
醇>
这很明显。缓冲区已满,其目的已实现,让我们将数据推送到设备。此外,可能有更多的数据来自该计划,我们需要为它腾出空间。
- 遇到换行符时。
醇>
有两种类型的设备:线路模式和块模式。此规则仅适用于线路模式设备(例如,终端)。写入磁盘时刷新换行符上的缓冲区没有多大意义。但是当程序写入终端时,这样做很有意义。在终端前面,用户不耐烦地等待输出。不要让他们等太多。
但为什么输出到终端需要缓冲?在终端上写字并不昂贵。当终端物理上位于处理器附近时,这是正确的。当终端和处理器分开一半并且用户通过远程连接运行程序时,也不是这样。
- 当有即将到来的输入时。
醇>
它应该显示为“当同一设备上存在阻止输入时”以使其清楚。
读取也被缓冲,原因与写作:效率相同。读取代码使用自己的缓冲区。它在需要时填充缓冲区scanf()
,其他输入读取函数从输入缓冲区获取数据。
当输入即将在同一设备上发生时,必须刷新缓冲区(实际写入设备的数据)以确保一致性。程序已将一些数据发送到输出,现在它希望读回相同的数据;这就是必须将数据刷新到设备的原因,以便读取代码在那里找到并加载它。
错误...缓冲是透明的,它不得影响应用程序行为。您的应用程序已将一些数据发送到输出。当应用程序退出时,数据必须在那里(在输出设备上)。
出于同样的原因,关闭相关文件时也会刷新缓冲区。这就是应用程序退出时发生的情况:清理代码关闭所有打开的文件(标准输入和输出只是从应用程序角度来看的文件),关闭强制刷新缓冲区。
答案 2 :(得分:2)
printf,缓冲的奇怪行为可以用下面的简单C代码来解释。请通读整个内容,并理解以下内容(有点棘手)
#include <stdio.h>
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语句移至while循环上方。
#include <stdio.h>
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添加到打印语句中,如下所示
#include <stdio.h>
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,在循环,scanf行,附加行,printf行中注释掉以获取打印结果
#include <stdio.h>
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;
}
观察:屏幕上显示“输入两个数字”行。
答案:
理查德·史蒂文斯(Richard Stevens)的书中描述了这种奇怪行为背后的原因。
打印到屏幕时打印
printf的工作是将输出写入stdout缓冲区。内核在以下情况时刷新输出缓冲区
默认情况下,标准输出设置为行缓冲 所以printf将不会打印,因为该行没有结束。 如果没有缓冲,则所有行均按原样输出。 然后,缓冲区已满,只有在缓冲区已满时才刷新。