繁体   English   中英

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

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

我最近阅读了很多关于标准 output 缓冲的内容。 我知道printf是缓冲的,但到目前为止,我认为它的缓冲区只有在将新行读入缓冲区或调用fflush(stdout)或调用printf的进程正常退出时才会被刷新。

我编写了这个程序,它调用 printf 在scanf之前没有换行。 当我用谷歌搜索时,我发现很多人说他们不明白为什么在 printf 之前执行了 scanf。 由于我现在了解标准输出缓冲的概念,这对我来说很有意义。

但是,就我而言,缓冲区在我运行 scanf 之前被刷新。 这样做确实有意义,因为用户可能希望 printf 在任何 scanf 之前执行,但它是如何发生的? 究竟什么是刷新标准输出? 是scanf吗?

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

(我正在运行 Arch Linux)

编辑:由于一些评论说我的系统的标准输出没有缓冲,我只想补充一点,没有在我的程序上运行scanf ,我的程序具有我上面提到的行为,它肯定是缓冲的。

这是实施质量问题。

stdin标准仅要求标准输入和stdout仅在附加到常规文件时才默认完全缓冲。 但它明确鼓励交互式设备的特定行为:

5.1.2.3 程序执行
[...]
对一致性实现的最低要求是:
[...]
交互设备的输入和 output 动态应按照 7.21.3 中的规定进行。 这些要求的目的是尽快出现无缓冲或行缓冲的 output,以确保在程序等待输入之前实际出现提示消息。

在许多 Posix 系统上, stdinstdout在连接到字符设备时是行缓冲的,然后当从stdin的读取尝试需要从底层系统句柄读取时, stdout会自动刷新。 即使没有尾随换行符,这也允许提示出现在终端上。

在 linux 上,此行为在stdio(3) linux 手册页中指定:

默认情况下,引用终端设备的 Output 流始终是行缓冲的; 每当读取引用终端设备的输入 stream 时,都会自动将待处理的 output 写入此类流。 如果在 output 终端上打印部分行后进行大量计算,则需要在关闭和计算之前 fflush(3) 标准 output 以便出现 Z78E6221F6393D1356814CE6F。

然而 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
    }

我认为只有在将新行读入缓冲区或调用fflush(stdout)或调用printf的进程正常退出时,才会刷新其缓冲区。

这不是 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),如 C 2018 7.21.3 3 中所述:

…当 stream 被行缓冲时,当遇到换行符时,字符将作为块传输到主机环境或从主机环境传输。 此外,当缓冲区被填满时,当在无缓冲的 stream 上请求输入时,或者在需要从主机环境传输字符的缓冲 stream 上请求输入时,字符将作为块传输到主机环境……

这仅表达了一个意图,标准进一步表示对这些特性的支持是实现定义的,因此这在技术上是一个实现质量问题。 但是,就诊断信息的质量而言,这不是质量问题。 关于支持是实现定义的,以及关于标准 output 是否是行缓冲的保留,很大程度上是对各种旧计算机系统中的可行性或可能性的让步。 在大多数现代 C 实现中,C 实现不应使用 C 标准的此许可证作为不实现这些功能的借口。

下面是一个示例,说明如何从不相关的 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:

#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.
}

如果我们删除setvbuffgetc ,“Hello”不会立即出现在终端上,这表明它是对无缓冲 stream 的读取,导致标准 output 被刷新。

我知道printf被缓冲了……

这取决于具体情况。 实际上是 stream 是否缓冲,C 2018 7.21.3 7 说:

…标准输入和标准 output 流是完全缓冲的,当且仅当可以确定 stream 不是指交互式设备时。

因此,如果您将程序的标准 output 重定向到文件,则它不指代交互式设备,因此如果程序可以检测到这一点,则必须完全缓冲。 当一个程序的 output 到一个交互式终端 window 时,它一定不能被完全缓冲。 (替代方案是行缓冲和非缓冲,典型的 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 是行缓冲的,并在调用scanf时刷新。

暂无
暂无

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

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