简体   繁体   English

在执行 scanf 之前,printf 如何被刷新?

[英]How is printf getting flushed before scanf is executed?

I have recently read a lot about standard output buffering.我最近阅读了很多关于标准 output 缓冲的内容。 I'm aware that printf is buffered but so far I thought that its buffer only gets flushed when a new line is read into the buffer or fflush(stdout) is called or the process that called printf exited normally.我知道printf是缓冲的,但到目前为止,我认为它的缓冲区只有在将新行读入缓冲区或调用fflush(stdout)或调用printf的进程正常退出时才会被刷新。

I wrote this program which calls printf without a new line just before scanf .我编写了这个程序,它调用 printf 在scanf之前没有换行。 When I googled that, I found a lot of people saying they didn't understand why scanf was executed before printf.当我用谷歌搜索时,我发现很多人说他们不明白为什么在 printf 之前执行了 scanf。 Since I now understand the concept of stdout buffering, that makes sense to me.由于我现在了解标准输出缓冲的概念,这对我来说很有意义。

However, in my case, the buffer is flushed just before I run scanf.但是,就我而言,缓冲区在我运行 scanf 之前被刷新。 It does make sense to do so because the user probably wants the printf to be executed before any scanf, but how is it happening?这样做确实有意义,因为用户可能希望 printf 在任何 scanf 之前执行,但它是如何发生的? What exactly is flushing stdout?究竟什么是刷新标准输出? Is it scanf?是scanf吗?

int main(void) {
    char things;
    printf("Hello ");
    scanf("%c", &things);
}

(I am running Arch Linux) (我正在运行 Arch Linux)

Edit: Due to some comments saying my system's stdout is unbuffered, I just want to add that without running scanf on my program, my program has exactly the behaviour I mentioned above, it is definitely buffered.编辑:由于一些评论说我的系统的标准输出没有缓冲,我只想补充一点,没有在我的程序上运行scanf ,我的程序具有我上面提到的行为,它肯定是缓冲的。

This is a quality of implementation issue.这是实施质量问题。

The C Standard only mandates that stdin and stdout be fully buffered by default only when attached to a regular file. stdin标准仅要求标准输入和stdout仅在附加到常规文件时才默认完全缓冲。 But it explicitly encourages specific behavior for interactive devices:但它明确鼓励交互式设备的特定行为:

5.1.2.3 Program execution 5.1.2.3 程序执行
[...] [...]
The least requirements on a conforming implementation are:对一致性实现的最低要求是:
[...] [...]
The input and output dynamics of interactive devices shall take place as specified in 7.21.3.交互设备的输入和 output 动态应按照 7.21.3 中的规定进行。 The intent of these requirements is that unbuffered or line-buffered output appear as soon as possible, to ensure that prompting messages actually appear prior to a program waiting for input.这些要求的目的是尽快出现无缓冲或行缓冲的 output,以确保在程序等待输入之前实际出现提示消息。

On many Posix systems, stdin and stdout are line buffered when attached to a character device and stdout is then flushed automatically when a read attempt from stdin requires reading from the underlying system handle.在许多 Posix 系统上, stdinstdout在连接到字符设备时是行缓冲的,然后当从stdin的读取尝试需要从底层系统句柄读取时, stdout会自动刷新。 This allows the prompt to appear on the terminal even without a trailing newline.即使没有尾随换行符,这也允许提示出现在终端上。

On linux, this behavior is specified in the stdio(3) linux manual page :在 linux 上,此行为在stdio(3) linux 手册页中指定:

Output streams that refer to terminal devices are always line buffered by default;默认情况下,引用终端设备的 Output 流始终是行缓冲的; pending output to such streams is written automatically whenever an input stream that refers to a terminal device is read.每当读取引用终端设备的输入 stream 时,都会自动将待处理的 output 写入此类流。 In cases where a large amount of computation is done after printing part of a line on an output terminal, it is necessary to fflush(3) the standard output before going off and computing so that the output will appear.如果在 output 终端上打印部分行后进行大量计算,则需要在关闭和计算之前 fflush(3) 标准 output 以便出现 Z78E6221F6393D1356814CE6F。

Yet the GNU libc has a subtly different behavior: only stdout is flushed this way as coded in glibc/libio/fileops.c (as modified by Ulrich Drepper on 2001-08-04 23:59:30):然而 GNU libc 有一个微妙的不同行为:只有stdout以这种方式刷新,如glibc/libio/fileops.c中的编码(由 Ulrich Drepper 在 2001-08-04 23:59:30 修改):

  /* Flush all line buffered files before reading. */
  /* FIXME This can/should be moved to genops ?? */
  if (fp->_flags & (_IO_LINE_BUF|_IO_UNBUFFERED))
    {
#if 0
      INTUSE(_IO_flush_all_linebuffered) ();
#else
      /* We used to flush all line-buffered stream.  This really isn't
         required by any standard.  My recollection is that
         traditional Unix systems did this for stdout.  stderr better
         not be line buffered.  So we do just that here
         explicitly.  --drepper */
      _IO_acquire_lock (_IO_stdout);

      if ((_IO_stdout->_flags & (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF))
          == (_IO_LINKED | _IO_LINE_BUF))
        _IO_OVERFLOW (_IO_stdout, EOF);

      _IO_release_lock (_IO_stdout);
#endif
    }

I thought that its buffer only gets flushed when a new line is read into the buffer or fflush(stdout) is called or the process that called printf exited normally.我认为只有在将新行读入缓冲区或调用fflush(stdout)或调用printf的进程正常退出时,才会刷新其缓冲区。

That is not the intent of the C standard.这不是 C 标准的意图。 When a stream is line buffered, output should also be flushed whenever the program requests input on any unbuffered stream or on a line buffered stream that is taking input from “the host environment” (such as a terminal window where the user types input), as stated in C 2018 7.21.3 3: When a stream is line buffered, output should also be flushed whenever the program requests input on any unbuffered stream or on a line buffered stream that is taking input from “the host environment” (such as a terminal window where the user types input),如 C 2018 7.21.3 3 中所述:

… When a stream is line buffered , characters are intended to be transmitted to or from the host environment as a block when a new-line character is encountered. …当 stream 被行缓冲时,当遇到换行符时,字符将作为块传输到主机环境或从主机环境传输。 Furthermore, characters are intended to be transmitted as a block to the host environment when a buffer is filled, when input is requested on an unbuffered stream, or when input is requested on a line buffered stream that requires the transmission of characters from the host environmen t…此外,当缓冲区被填满时,当在无缓冲的 stream 上请求输入时,或者在需要从主机环境传输字符的缓冲 stream 上请求输入时,字符将作为块传输到主机环境……

This only expresses an intent, and the standard further says support for these characteristics is implementation-defined, so this is technically a quality-of-implementation issue.这仅表达了一个意图,标准进一步表示对这些特性的支持是实现定义的,因此这在技术上是一个实现质量问题。 However, it is not a quality issue in the sense of how good your diagnostic messages are.但是,就诊断信息的质量而言,这不是质量问题。 The reservations about support being implementation-defined and about, as discussed below, whether standard output is line-buffered are largely concessions to feasibility or possibility in various old computer systems.关于支持是实现定义的,以及关于标准 output 是否是行缓冲的保留,很大程度上是对各种旧计算机系统中的可行性或可能性的让步。 In most modern C implementations, a C implementation should not use this license by the C standard as an excuse not to implement these features.在大多数现代 C 实现中,C 实现不应使用 C 标准的此许可证作为不实现这些功能的借口。

Here is an example of how reading input from an unrelated stream can flush standard output.下面是一个示例,说明如何从不相关的 stream 读取输入可以刷新标准 output。 When I execute this program on macOS 10.14.6 using Xcode 11.3.1, the “Hello” in standard output is flushed when the unrelated stream to /dev/null is read, but not when output is merely written with printf with no read: When I execute this program on macOS 10.14.6 using Xcode 11.3.1, the “Hello” in standard output is flushed when the unrelated stream to /dev/null is read, but not when output is merely written with printf with no read:

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


int main(void)
{
    printf("Hello");
    FILE *dummy = fopen("/dev/null", "r");
    setvbuf(dummy, NULL, _IONBF, 0); // Make dummy unbuffered.
    fgetc(dummy);       // "Hello" appears on terminal.
    printf(" world.");  // " world." does not appear on terminal.
    sleep(5);
    printf("\n");       // " world." appears on terminal.
}

If we remove either the setvbuf or the fgetc , the “Hello” does not appear on the terminal immediately, demonstrating that it is a read to the unbuffered stream that causes standard output to be flushed.如果我们删除setvbuffgetc ,“Hello”不会立即出现在终端上,这表明它是对无缓冲 stream 的读取,导致标准 output 被刷新。

I'm aware that printf is buffered…我知道printf被缓冲了……

This depends on circumstances.这取决于具体情况。 It is actually the stream that is buffered or not, and C 2018 7.21.3 7 says:实际上是 stream 是否缓冲,C 2018 7.21.3 7 说:

… the standard input and standard output streams are fully buffered if and only if the stream can be determined not to refer to an interactive device. …标准输入和标准 output 流是完全缓冲的,当且仅当可以确定 stream 不是指交互式设备时。

Thus, if you redirect a program's standard output to a file, it does not refer to an interactive device, so it must be fully buffered if the program can detect this.因此,如果您将程序的标准 output 重定向到文件,则它不指代交互式设备,因此如果程序可以检测到这一点,则必须完全缓冲。 When a program's output is going to an interactive terminal window, then it must not be fully buffered.当一个程序的 output 到一个交互式终端 window 时,它一定不能被完全缓冲。 (The alternatives are line buffered and unbuffered, and typical C implementations use line buffered.)' (替代方案是行缓冲和非缓冲,典型的 C 实现使用行缓冲。)

So, if standard input and standard output are both connected to an interactive device (and the cannot detect otherwise), then printf output should appear before scanf is executed, either because standard output is unbuffered (so the printf output appears immediately) or because standard output is line buffered and is flushed when scanf is called. So, if standard input and standard output are both connected to an interactive device (and the cannot detect otherwise), then printf output should appear before scanf is executed, either because standard output is unbuffered (so the printf output appears immediately) or because standard output 是行缓冲的,并在调用scanf时刷新。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM