簡體   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