为什么这个程序的输出是无序的并写入标准输出?

Why the output of this program is unordered and written to stdout?

在这个程序中,输出是无序的,因为 stdout 链接到终端,包含 stdoutfprintf() 的输出被写入终端,但是为什么输出包含 stderrfprintf() 也打印到终端?相反,它应该打印到 stderr 文件句柄。 stderr是否也链接到终端?

代码

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

int main() {
    fprintf(stdout, "This is to stdout. ");
    fprintf(stderr, "This is to stderr. ");
    fprintf(stdout, "This is also to stdout. ");
    return 0;
}

OUTPUT(在在线编译器中)

This is to stderr. This is to stdout. This is also to stdout.

另外我想问一件事。为什么我的 Visual Studio Code IDE.

中的输出不同

VS 代码输出

This is to stdout. This is to stderr. This is also to stdout.

这里VS CODE给出了与在线编译器不同的输出。为什么会这样以及如何解决这个问题?

@Eric 告诉我我的回答是错误的。 :(

Stdout 和stderr 在逻辑上是不同的。可以将它们重定向到不同的设备,例如打印机和屏幕。 指令序列必须遵循程序中分号所暗示的顺序。 因此,第一个字符串被发送到标准输出。之后,第二个字符串被发送到 stderr。 之后,第三个字符串被发送到标准输出。

如果您将字符串发送到屏幕和打印机,则字符串会发送到各自的端​​口,但这些设备进行各自打印所用的时间是独立的,并且不受您的程序控制. 考虑打印机有待处理工作的情况。

但是,默认情况下,您将 stderr 和 stdout 定向到同一设备:屏幕。 这个事实并不意味着字符串必须按顺序显示。 操作系统可能会以不同的方式处理在 stderr 和 stdout 上打印的请求。 这些字符串在逻辑上被发送到不同的设备,这些设备在物理上产生相同的结果,但是 OS 出于某种原因可以优先考虑 stderr。
stderr 流用于通知错误消息。
这是一个特殊而危急的情况。目的是文本尽快到达,所以字符不缓冲,一到达就发送。
另一方面,发送到 stdout 的文本被缓冲,因此字符以块或整行的形式发送。 如果 OS 检测到文本被发送到 stderr,也许它会优先处理它。

解决方案是仅在发生严重错误时才使用 stderr。

在您的 C 实现中,当输出到终端时,stdout 行缓冲,而 stderr 非缓冲。这意味着 stderr 的输出会立即发送到终端,而 stdout 的输出仅在以下情况下发送:

  • 发送一个换行符,
  • 请求用户输入1(因此在 stdout 上发送的提示将在用户输入响应之前出现),
  • 缓冲区已满,或者
  • 请求刷新缓冲区(与 fflush 一样)。

缓冲流时,字符可能会累积在缓冲区中,并在缓冲区已满时作为块发送或接收。使用缓冲是因为发送或接收数据的操作有很大的开销,因此为每个发送的字符或每个部分行执行它们在执行时间和能量等资源方面将是昂贵的。在一次操作中从缓冲区发送多个字符效率更高。

除了无缓冲和行缓冲之外,还有完全缓冲,其中仅当缓冲区已满时才发送或接收字符。 C 标准在 C 2018 7.21.3 3.

中描述了缓冲

在7.21.3 7中,标准告诉我们标准错误流没有完全缓冲。它可能是无缓冲的或行缓冲的。您的 C 实现显然选择使其无缓冲,因此错误消息将立即写入终端。该段还告诉我们,只有当流“可以确定不指向交互式设备”时,标准输入和标准输出才会被完全缓冲。例如,如果您将输出重定向到文件而不是终端,则它不是指交互式设备,它应该是完全缓冲的。当已知输出将进入交互式设备时,不应对其进行完全缓冲。它可能是行缓冲的或未缓冲的,并且您的 C 实现显然选择使其行缓冲。

如果您修改程序以在它打印的每个字符串的末尾包含 \n,您将按顺序看到它们,因为无缓冲和行缓冲流将打印每个完整的行(字符以换行符)顺序。

您还可以使用 setvbuf 在流打开之后和对其执行任何其他操作之前更改流的缓冲模式。例如,要将 stdout 设置为无缓冲,您可以使用:

int result = setvbuf(stdout, NULL, _IONBF, 0);
if (result != 0)
    fprintf(stderr, "Error, setvbuf returned %d.\n", result);

setvbuf 在 C 2018 7.21.5.6 中指定。 _IOFBF 请求完全缓冲,_IOLBF 请求行缓冲。 (第二个和第四个参数用于提供您自己的数组用作缓冲区及其长度。)

脚注

1 具体来说,在无缓冲流或“需要从主机环境传输字符”将触发行缓冲输出的刷新。