【问题标题】:Difference between FILE * "/dev/stdout" and stdoutFILE * "/dev/stdout" 和标准输出的区别
【发布时间】:2015-07-31 06:34:59
【问题描述】:

让我们看看这个Hello World程序

#include <stdio.h>
int main(int argc, char ** argv) {
    printf("Hello, World!");

    const char* sFile = "/dev/stdout"; // or /proc/self/fd/0
    const char* sMode = "w";
    FILE * output = fopen(sFile, sMode);
    //fflush(stdout) /* forces `correct` order */
    putc('!', output); // Use output or stdout from stdio.h

    return 0;
}

使用output 文件描述符编译时,输出为:

!Hello, World!

当使用stdio.h 提供的stdout 文件描述符编译时,输出如预期:

Hello, World!!

我认为当用后者调用putc 时,它会直接 打印到stdout,并且当在/dev/stdout 上使用文件描述符时,它会打开一个管道 并打印进去。不过我不确定。

这种行为更有趣,因为它不会覆盖 'Hello' 的第一个字符,而是将自身推入行缓冲区的第一个位置,位于已推入的字符串前面。

从逻辑的角度来看,这是出乎意料的。

谁能解释一下这里到底发生了什么?


我正在使用 cc (Ubuntu 4.8.2-19ubuntu1) 4.8.2 和使用 gcc 4.8.2 编译的 3.13.0-52 linux 内核


编辑:我已经完成了两个程序的strace,这是重要的部分:

没有fflush(stdout) 场景的output (fopen("/dev/stdout", "w")) 产生:

...
open("/dev/stdout", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f62f21e9000
write(3, "!", 1!)                        = 1
write(1, "Hello, World!", 13Hello, World!)           = 13
exit_group(0)                           = ?

使用fflush(stdout) 产生并强制执行正确的顺序:

...
open("/dev/stdout", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
write(1, "Hello, World!", 13Hello, World!)           = 13
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5ad4557000
write(3, "!", 1!)                        = 1
exit_group(0)                           = ?

stdout来自 stdlib.h)场景产生:

...
write(1, "Hello, World!!", 14Hello, World!!)          = 14
exit_group(0)                           = ?

看来FILE * output = fopen("/dev/stdout") 流使用的文件描述符与stdout 不同 同样,printf 使用 stdout 因此,在第三种情况下,字符串在被推送到流上之前被组装。

【问题讨论】:

  • stdout 是 FILE *,而不是文件描述符。同样,output 不是文件描述符。每个都有一个底层文件描述符,如果您直接写入它,您将看不到这种行为。 (直接写入文件描述符会绕过缓冲。)
  • 最大的不同是每个FILE*都使用自己的缓冲区,彼此无关..

标签: c linux libc


【解决方案1】:

/dev/stdout/proc/self/fd/0 不同。事实上,如果您没有足够的权限,您将无法写入/dev/stdout,因为/dev/stdout 不是Linux 中的任何标准字符设备,任何使用"w" 选项打开它的尝试都会尝试创建该目录上的实际常规文件。你要找的字符设备是/dev/tty

在C语言中,stdout是一个初始化的FILE *类型的全局变量,它指向标准输出文件,即描述符为1的文件。stdout只存在于C命名空间中,并不存在' t 与名为 "stdout" 的任何实际文件相关

【讨论】:

  • 我不知道您使用的是什么发行版,但/dev/stdout 是指向/proc/self/fd/1 的符号链接,它本身就是指向我的/dev/pts/XX 的符号链接。所以它们是可以互换的。最后,这甚至不是问题:)
  • 但是,值得一提的是,/dev/stdout、/proc/self/fd 或 /dev/fd 都不是标准化的,而 /dev/tty 是。
【解决方案2】:

两个流(stdoutoutput)都被缓冲。在它们被刷新之前实际上什么都不会被写入。由于您没有明确刷新它们,也没有安排它们自动刷新,因此它们仅在它们关闭时才会自动刷新。

您也没有显式关闭它们,因此它们被标准库的 on_exit 钩子关闭(并刷新)。正如 William Pursell 正确指出的那样,没有指定缓冲 I/O 流的关闭顺序。

查看fflush(3)fclose(3)setbuf(3) 手册页,了解有关控制何时以及如何刷新输出的更多信息。

【讨论】:

  • 关键点是它们被关闭的顺序是未指定的。
  • output 场景中在putc(...) 之前添加fflush(stdout) 不会改变行为。
  • 它对我有用,非常可靠。我建议在生成的可执行文件上运行 strace 以查看生成的系统调用序列。无论如何,这对于了解缓冲 I/O 函数如何与系统交互非常有启发性。
  • 虽然官方没有指定顺序,但 on_exit 钩子是按相反的顺序调用的:后面创建的会先调用,
  • @MrPaulch 你的编辑有fflush(output),但应该是fflush(stdout)
猜你喜欢
  • 2015-08-27
  • 2012-07-20
  • 1970-01-01
  • 2011-10-04
  • 2012-10-05
  • 2012-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多