Разница между файлами */dev/stdout и stdout

Посмотрим на эту программу 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!

при компиляции с использованием дескриптора файла stdout, предоставленного stdio.h, результат выглядит так, как ожидалось:

Hello, World!!

Я полагаю, что при вызове putc с последним он будет печататься непосредственно в stdout, а при использовании дескриптора файла на /dev/stdout он откроет pipe и напечатает на нем. Я не уверен, хотя.

Поведение еще более интересно, так как оно не перезаписывает первый символ "Hello", а скорее вставляет себя в первую позицию буфера строк перед уже нажатой строкой.

С логической точки зрения это неожиданно неожиданно.

Кто-нибудь может объяснить, что именно происходит здесь?


Я использую cc (Ubuntu 4.8.2-19ubuntu1) 4.8.2 и ядро ​​linux 3.13.0-52, скомпилированное w/gcc 4.8.2


Изменить: я выполнил strace обеих программ, и вот важная часть:

output (fopen ( "/dev/stdout", "w" )) без сценария fflush(stdout) создает:

...
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 Таким образом, в третьем сценарии строка собирается до того, как она будет нажата на поток.

Ответ 1

Оба потока (stdout и output) буферизуются. Ничто на самом деле не написано, пока они не покраснели. Поскольку вы явно не очищаете их, и не устанавливаете для них автоматическое покраснение, они только автоматически сбрасываются, когда они закрыты.

Вы также явно не закрываете их, поэтому они закрываются (и краснеют) с помощью стандартной библиотеки on_exit. Как правильно указал Уильям Пурселл, порядок, в котором потоки буферизованного ввода-вывода закрыты, не указывается.

Просмотрите страницы руководства fflush(3), fclose(3) и setbuf(3) для получения дополнительной информации о контроле того, когда и как очищается ваш выход.

Ответ 2

/dev/stdout не совпадает с /proc/self/fd/0. На самом деле, если у вас недостаточно привилегий, вы не сможете писать на /dev/stdout, поскольку /dev/stdout не является стандартным символьным устройством в Linux, и любая попытка его с помощью опции "w" попытается создать фактический обычный файл в этом каталоге. Символьное устройство, которое вы ищете, /dev/tty

В языке C stdout представляет собой инициализированную глобальную переменную типа FILE *, которая указывает на стандартный выходной файл, то есть файл с дескриптором 1. stdout существует только в пространстве имен C и не имеет значения ' t относятся к любому фактическому файлу с именем "stdout"