简体   繁体   中英

How is printf getting flushed before scanf is executed?

I have recently read a lot about standard output buffering. 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.

I wrote this program which calls printf without a new line just before scanf . When I googled that, I found a lot of people saying they didn't understand why scanf was executed before printf. 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. 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? What exactly is flushing stdout? Is it scanf?

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

(I am running 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.

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. But it explicitly encourages specific behavior for interactive devices:

5.1.2.3 Program execution
[...]
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. 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.

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. 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 :

Output streams that refer to terminal devices are always line buffered by default; pending output to such streams is written automatically whenever an input stream that refers to a terminal device is read. 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.

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):

  /* 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.

That is not the intent of the C standard. 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 , characters are intended to be transmitted to or from the host environment as a block when a new-line character is encountered. 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…

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

Here is an example of how reading input from an unrelated stream can flush standard 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.
}

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.

I'm aware that printf is buffered…

This depends on circumstances. It is actually the stream that is buffered or not, and C 2018 7.21.3 7 says:

… 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.

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. When a program's output is going to an interactive terminal window, then it must not be fully buffered. (The alternatives are line buffered and unbuffered, and typical C implementations use line buffered.)'

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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