为什么printf在调用后不刷新,除非格式字符串中有换行符?这是POSIX行为吗?我如何让printf每次都立即刷新?
printf
Stdout是缓冲的,因此只在打印换行符后输出。
要获得立即输出,可以:
要立即刷新,请调用fflush(stdout)或fflush(NULL) (NULL表示刷新所有内容)。
fflush(stdout)
fflush(NULL)
NULL
你可以fprintf到stderr,这是无缓冲的。或者您可以在需要时刷新stdout。或者你可以将stdout设置为unbuffered。
stdout流在默认情况下是行缓冲的,因此只会在它到达换行符后(或当它被告知时)显示缓冲区中的内容。你有几个选项可以立即打印:
stdout
stderr
fprintf
fprintf(stderr, "I will be printed immediately");
fflush
printf("Buffered, will be flushed"); fflush(stdout); // Will now print everything in the stdout buffer
setbuf
setbuf(stdout, NULL);
setvbuf
setvbuf(stdout, NULL, _IONBF, 0);
这样做可能是为了提高效率,也因为如果有多个程序写入一个TTY,这样就不会让一行上的字符相互交错。所以如果程序A和B输出,你通常会得到:
program A output program B output program B output program A output program B output
这太糟糕了,但总比
proprogrgraam m AB ououtputputt prproogrgram amB A ououtputtput program B output
请注意,它甚至不保证在换行上刷新,因此如果刷新对您很重要,则应该显式刷新。
默认情况下,stdout是行缓冲,stderr是无缓冲,文件是完全缓冲。
注意:微软运行时库不支持行缓冲,所以printf("will print immediately to terminal"):
printf("will print immediately to terminal")
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setvbuf
不,这不是POSIX行为,而是ISO行为(好吧,它是是 POSIX行为,但仅限于它们符合ISO)。
标准输出是行缓冲的,如果它可以检测到指向交互设备,否则它是完全缓冲的。因此,在某些情况下,printf不会刷新,即使它得到一个换行符发送出去,例如:
myprog >myfile.txt
这对于效率是有意义的,因为如果你与用户交互,他们可能想看到每一行。如果您将输出发送到一个文件,很可能另一端没有用户(尽管也不是不可能,他们可能在跟踪文件)。现在你认为可以用户想要看到每个字符,但这有两个问题。
首先是效率不高。其次,最初的ANSI C要求主要是编纂现有的行为,而不是发明新行为,而且这些设计决策早在ANSI开始这个过程之前就已经做出了。现在,即使是ISO在改变标准中的现有规则时也非常谨慎。
至于如何处理这个问题,如果在您希望立即看到的每个输出调用之后使用fflush (stdout),就可以解决问题。
fflush (stdout)
或者,你可以在操作stdout之前使用setvbuf,将其设置为unbuffered,你就不必担心将所有这些fflush行添加到你的代码中:
setvbuf (stdout, NULL, _IONBF, BUFSIZ);
请记住,如果您是将输出发送到文件,可能会对性能产生很大影响。还要记住,对此的支持是由实现定义的,而不是由标准保证的。
ISO C99 section 7.19.3/3是相关的位:
7.19.3/3
当流为无缓冲的时,字符将尽可能快地从源或目的地出现。否则,字符可能被累积起来,并作为一个块传输到主机环境或从主机环境传输出去。 当流为完全缓冲时,当缓冲区被填充时,字符将作为块传输到主机环境或从主机环境传输。 当流为行缓冲时,当遇到换行字符时,字符将作为块传输到主机环境或从主机环境传输。 此外,当缓冲区被填充时,当在未缓冲流上请求输入时,或者当在行缓冲流上请求输入时,需要从主机环境传输字符时,字符将作为块传输到主机环境。 对这些特性的支持是由实现定义的,并且可能会受到setbuf和setvbuf函数的影响。
当流为无缓冲的时,字符将尽可能快地从源或目的地出现。否则,字符可能被累积起来,并作为一个块传输到主机环境或从主机环境传输出去。
当流为完全缓冲时,当缓冲区被填充时,字符将作为块传输到主机环境或从主机环境传输。
当流为行缓冲时,当遇到换行字符时,字符将作为块传输到主机环境或从主机环境传输。
此外,当缓冲区被填充时,当在未缓冲流上请求输入时,或者当在行缓冲流上请求输入时,需要从主机环境传输字符时,字符将作为块传输到主机环境。
对这些特性的支持是由实现定义的,并且可能会受到setbuf和setvbuf函数的影响。
使用setbuf(stdout, NULL);禁用缓冲。
一般有2级缓冲-
1. 内核缓冲区缓存(使读/写更快)
2. I/O库中的缓冲(减少no。系统调用)
让我们以fprintf and write()为例。
fprintf and write()
fprintf()
setbuff(stream,NULL)
fflush(FILE *stream)
现在我们在内核缓冲区中。内核(/OS)希望最小化磁盘访问时间,因此它只读取/写入磁盘块。因此,当发出read()(这是一个系统调用,可以直接或通过fscanf()调用)时,内核从磁盘读取磁盘块并将其存储在缓冲区中。之后,数据从这里复制到用户空间。
read()
fscanf()
类似地,从I/O缓冲区接收到的fprintf()数据由内核写入磁盘。这使得read() write()更快。
现在强制内核启动write(),之后数据传输由硬件控制器控制,也有一些方法。我们可以在写调用期间使用O_SYNC或类似的标志。或者,我们可以使用其他函数,如fsync(),fdatasync(),sync(),让内核在内核缓冲区中有数据时立即启动写操作。
write()
O_SYNC
fsync(),fdatasync(),sync()