【问题标题】:Why does write() print before printf() in output redirection?为什么在输出重定向中 write() 在 printf() 之前打印?
【发布时间】:2020-03-26 14:56:39
【问题描述】:

所以我知道printf()write() 更高级别并最终使用write()Printf() 被缓冲,write() 进行系统调用。

示例 1,如果我要在 write() 之前使用 printf() 运行程序,那么它将在 write() 的值之前输出 printf() 的值。

示例 2,如果我要运行相同的程序并让它通过输出重定向到一个文件,write() 的值在 printf() 之前输出。

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

int main()
{
    printf("This is a printf test\n");
    write(STDOUT_FILENO, "This is a write test\n", 21);
    return 0;
}

我不明白这里发生了什么。在示例 1 中,程序是否在运行 write() 之前等待 printf()s 输出?在示例 2 中,程序是否重定向了第一个准备好的输出?又因为write()是低级,不需要像printf()那样缓冲,所以先打印?

【问题讨论】:

  • 查找setvbuf()_IONBF_IOLBF_IOFBF的含义。当输出到终端时,你会得到_IOLBF 行为;当输出到文件时,你会得到_IOFBF 行为。

标签: c


【解决方案1】:

您回答了自己的问题。

printf 被缓冲,write 没有。

对于终端的输出,C stdio 系统有一个特性,它会在看到换行符'\n' 时刷新缓冲区。有关 stdio 缓冲的更多信息,请查看setvbuf 的文档。

但是当输出到文件以提高速度时,stdio 系统不会刷新缓冲区。这就是为什么write 输出首先出现的原因。

这是在我的 Linux 系统上运行的一些 strace 输出:

fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7880b41000
write(1, "这是一个 printf 测试\n", 22) = 22
write(1, "这是一个写测试\n\0", 22) = 22

fstat 是 stdio 系统检测输出文件描述符 1 连接到的类型的位置。我相信它看着st_mode 并看到它是一个字符设备。磁盘文件将是块设备。 mmap 是为 stdio 缓冲区分配的内存,为 4K。然后写入输出。

【讨论】:

  • 我感觉不清楚。 Matteo 的回答是usually the buffering done by libc is disabled (or a flush is forced at every carriage return)。但是,您的回答说the C stdio system has a feature that it flushes the buffers whenever it sees a newline '\n'. printf 总是进入缓冲区,对吗?只是回车时是否刷新?
  • 在输出重定向的例子中,这是内存分配的事情吗?这意味着当系统在等待printf 时,它会先运行写入,对吗?对于常规程序运行,C 是否在运行下一个命令之前检查回车符?我了解输出的行为,但它是如何深入工作的。我希望我能够传达我想要询问和理解的内容。
  • @Doug:我相信大多数实现将输出和缓冲区处理分开。所以printf 对缓冲一无所知。它调用写入缓冲区的内部函数。然后,如果它处于行缓冲区模式,这些函数会检查缓冲区是否有换行符。如果他们找到一个,则该行将发送到带有write 的系统。如果它处于完全缓冲模式,它只检查一个完整的缓冲区,而不是换行符。
  • @Doug:这不是内存分配的事情。什么都没有被等待,确切地说。在完全缓冲模式下发生的事情是将 printf 输出写入缓冲区。然后调用 write 并立即写入 write 输出。然后程序退出。在退出期间,缓冲区被刷新并写出。
  • @Doug:不是堆栈。堆栈会使其以相反的顺序输出。输出缓冲区为 FIFO。先进先出。
【解决方案2】:

这与 C 标准库完成的输出缓冲有关。在第一种情况下,由于您在终端上编写,所以 libc 完成的缓冲是面向行的(每次回车时都会强制刷新),以立即在屏幕上显示文本,优先考虑交互性而不是性能(这不应该是一个问题,因为终端不应成为文本负载的目标)。正因为如此,printf 输出会立即与一些 write 调用一起写入,这发生在您明确进行下一个调用之前。

在第二种情况下,libc 检测到您正在写入文件,因此,为了提高性能,它启用了缓冲;因此,通常第一个 printf 不会立即提交,而您的 write 将在 libc 实际刷新缓冲区之前发生。

再说一次,这通常会发生。我不认为任何标准都强制要求这种行为编辑: 实际上,这是 C99 强制要求的,请参阅@Jonathan 的评论)(并且在第二个示例中,即使启用了缓冲,库也可能决定执行刷新,例如,如果缓冲区被您的 printf 填充)。

【讨论】:

  • 该行为由标准强制执行。在 C99, §7.19.3, ¶3 当一个流被完全缓冲时,当缓冲区被填满时,字符打算作为一个块传输到主机环境或从主机环境传输。当流被行缓冲时,当遇到换行符时,字符将作为块传输到主机环境或从主机环境传输。和¶7 最初打开时,标准错误流是没有完全缓冲;当且仅当可以确定流不引用交互式设备时,标准输入和标准输出流才会被完全缓冲。
  • @JonathanLeffler:嗯,很高兴知道,我没有找到,因为我在寻找“终端”而不是“交互式设备”。我修正了我的答案。
【解决方案3】:

内部printf 将在其缓冲区已满时使用write。如果它检测到它正在写入交互式输出(例如尚未重定向的标准输出),它也可能在缓冲区已满之前执行写入操作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-07
    • 2020-01-10
    • 2016-05-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多