Относительно печати символов в C

int main()
{
    printf("Hello"); // doesn't display anything on the screen

    printf("\n"); // hello is display on the screen

    return 0;
}

Все символы (кандидаты на печать) буферизуются до тех пор, пока не будет получена новая строка? Правильный?

Q1 - Почему он ждет перед печатью на терминале до символа новой строки?

Q2 - Где буферизуются символы первого printf (т.е. "Hello")?

Q3 - Каков поток печати printf()->puts()->putchar() -> где теперь? Водитель? Есть ли у драйвера возможность дождаться \n?

Q4. Какова роль стандартного вывода, связанного с процессом?

Ищем подробную картину. Не стесняйтесь редактировать вопрос, если что-то не имеет смысла.


person codey modey    schedule 27.02.2014    source источник
comment
Чтобы ответить на ваш непомеченный вопрос, это зависит от системы и ваших настроек. Иногда даже новая строка не вызывает сброса, но выполнение fflush(stdout) приводит к сбросу.   -  person Nathan S.    schedule 27.02.2014
comment
Хотел бы я изучить fflush раньше   -  person codey modey    schedule 27.02.2014


Ответы (4)


Я начну с некоторых определений, а затем перейду к ответам на ваши вопросы.

Файл: это упорядоченная последовательность байтов. Это может быть файл на диске, поток байтов, сгенерированный программой (например, конвейером), сокет TCP/IP, поток байтов, полученных или отправленных на периферийное устройство (например, клавиатуру или дисплей) и т. д. Последние два являются интерактивными файлами. Файлы обычно являются основным средством, с помощью которого программа взаимодействует со своим окружением.

Поток: представление потока данных из одного места в другое, например, с диска в память, из памяти на диск, одной программы. к другому и т. д. Поток — это источник данных, в который данные можно вводить (записывать) или извлекать данные (читать). Таким образом, это интерфейс для записи данных или чтения данных из файла, который может быть любого типа, как указано выше. Прежде чем вы сможете выполнить какую-либо операцию с файлом, файл должен быть открыт. Открытие файла связывает его с потоком. Потоки представлены типом данных FILE, определенным в заголовке stdio.h. Объект FILE (это структура) содержит всю внутреннюю информацию о состоянии подключения к связанному файлу, включая такие вещи, как индикатор позиции файла и информацию о буферизации. Объекты FILE выделяются и управляются внутри функциями библиотеки ввода/вывода, и вам не следует пытаться создавать свои собственные объекты типа FILE, библиотека делает это за нас. Программы должны иметь дело только с указателями на эти объекты (FILE *), а не с самими объектами.

Буфер. Буфер — это блок памяти, который принадлежит потоку и используется для временного хранения данных потока. Когда над файлом происходит первая операция ввода/вывода, вызывается malloc и получается буфер. Символы, которые записываются в поток, обычно накапливаются в буфере (перед передачей в файл фрагментами), а не появляются сразу после вывода прикладной программой. Точно так же потоки извлекают входные данные из хост-среды блоками, а не посимвольно. Это сделано для повышения эффективности, так как файловый и консольный ввод-вывод медленнее по сравнению с операциями с памятью.

Библиотека C предоставляет три предопределенных текстовых потока (FILE *), открытых и доступных для использования при запуске программы. Это stdin (стандартный поток ввода, который является обычным источником ввода для программы), stdout (стандартный поток вывода, который используется для нормального вывода программы) и stderr (стандартный поток ошибок, который используется для сообщений об ошибках и диагностики, выдаваемой программой). Будут ли эти потоки буферизированы или небуферизованы, определяется реализацией и не требуется стандартом.

GCC обеспечивает три типа буферизации: небуферизованная, блочная буферизация и буферизация строк. Небуферизованный означает, что символы появляются в целевом файле, как только они записываются (для выходного потока), или входные данные считываются из файла посимвольно, а не блоками (для входных потоков). Блочная буферизация означает, что символы сохраняются в буфере и записываются или считываются как блок. Буферизация строк означает, что символы сохраняются только до тех пор, пока новая строка не будет записана в буфер или прочитана из него.

stdin и stdout буферизуются блоками тогда и только тогда, когда можно определить, что они не относятся к интерактивному устройству, в противном случае они буферизуются строками (это верно для любого потока). stderr по умолчанию всегда не буферизируется.

Стандартная библиотека предоставляет функции для изменения поведения потоков по умолчанию. Вы можете использовать fflush, чтобы принудительно вывести данные из буфера выходного потока (fflush не определено для входных потоков). Вы можете сделать поток небуферизованным, используя функцию setbuf.

Теперь давайте перейдем к вашим вопросам.

Вопрос без пометки: Да, потому что stdout обычно относится к терминалу с дисплеем, если у вас нет перенаправления вывода с использованием оператора >.

Q1: Он ждет, потому что stdout буферизуется новой строкой, когда он ссылается на терминал.

Q2: Символы буферизуются, ну, в буфере, выделенном для потока stdout.

Q3: Поток печати: память --> stdout буфер --> терминал дисплея. Существуют буферы ядра, которые также контролируются ОС, через которые данные проходят перед тем, как появиться на терминале.

Q4: stdout относится к стандартному выходному потоку, который обычно является терминалом.

Наконец, вот пример кода для экспериментов, прежде чем я закончу свой ответ.

#include <stdio.h>
#include <limits.h>

int main(void) {
    // setbuf(stdout, NULL);     // make stdout unbuffered
    printf("Hello, World!");     // no newline
    // printf("Hello, World!");  // with a newline

    // only for demonstrating that stdout is line buffered

    for(size_t i = 0; i < UINT_MAX; i++)
        ;                        // null statement

    printf("\n");                // flush the buffer
    return 0;
}
person ajay    schedule 27.02.2014
comment
Это отличный ответ, не могли бы вы также включить некоторые ссылки на соответствующие коды и документы для чтения. - person codey modey; 28.02.2014
comment
Спасибо :) Буду включать соответствующие ссылки на ресурсы и ссылки, когда у меня будет свободное от работы время. - person ajay; 28.02.2014

printf пишет не напрямую на экран, а в выходной поток, который по умолчанию буферизован. Причина этого в том, что может даже не быть прикрепленного экрана, а вывод может также идти в файл. По соображениям производительности для системы лучше, если доступ к диску буферизуется, а затем выполняется за один шаг с фрагментами соответствующего размера, а не записывается каждый раз.

Вы даже можете изменить размер буфера и установить его равным 0, что означает, что весь вывод направляется непосредственно в цель, что может быть полезно для целей ведения журнала.

setbuf(stdout, NULL);

Буфер сбрасывается либо когда он заполнен, либо если выполняются определенные критерии, например печать новой строки. Поэтому, когда вы будете выполнять printf в цикле, вы заметите, что он будет записываться кусками, если между ними нет новой строки.

person Devolus    schedule 27.02.2014
comment
Вы смешиваете буферизацию строки (например, при подключении к терминалу) и полную буферизацию (например, при выводе в файл) вместе, это НЕ одно и то же. Сброс при обнаружении новой строки - это только поведение буферизованного потока строки. - person Yu Hao; 27.02.2014
comment
Это зависит от ОС, с которой вы работаете. В среде UNIX обычно нет большой разницы, и вы можете увидеть этот эффект также в сеансе терминала. В Windows при печати на консоль он обычно всегда печатается напрямую, но это может варьироваться в зависимости от реализации, и на него не следует полагаться. - person Devolus; 27.02.2014
comment
Нет, они совсем НЕ одинаковы. Например, при перенаправлении стандартного вывода в файл содержимое не сбрасывается, даже если вы добавляете новый символ строки, оно сбрасывается только при заполнении буфера или при явном запросе (с использованием fflush()). Различные реализации могут иметь другой тип буфера по умолчанию. Но поведение трех типов буферов определено в стандарте C. - person Yu Hao; 27.02.2014
comment
Что такое выходной поток? Как это реализовано? Где это реализовано? - person codey modey; 27.02.2014
comment
@codeymodey, поток реализован в вашей библиотеке C, на которую вы ссылаетесь. - person Devolus; 27.02.2014
comment
@Devolus Итак, все это управление буферизацией происходит в моем коде или библиотеке C (из пользовательского пространства), без участия ядра. Как только символ передается ядру, он печатается? Правильный? - person codey modey; 27.02.2014
comment
Стримбуфер есть, да. Однако обычно в операционной системе также есть буфер, чтобы избежать доступа к диску, экрану или сети или куда-либо еще, куда направляется ваш поток. - person Devolus; 27.02.2014
comment
@Devolus Хорошо, я понял. printf будет записывать все в буфер (массив, т.е. стандартный вывод), который реализован самим кодом, поэтому ядру (т.е. драйверу) ничего не передается для печати. Теперь, как только код printf '\n' сбрасывает буфер в драйвер для печати и начинает сохранять символы для следующей строки. Я прав? Является ли stdout действительно массивом? Я всегда думал, что это файл? - person codey modey; 27.02.2014
comment
@codeymodey, структуры потоков представляют собой массив, и в реализации потока у них есть буфер, связанный с каждым потоком, и дескриптор файла, который указывает на фактическую цель, поэтому, когда вы очищаете поток, он выполняет соответствующее действие на целевом устройстве. Если вас интересуют подробности, я предлагаю вам взглянуть на реализации с открытым исходным кодом для стандартных c-библиотек gcc. - person Devolus; 27.02.2014
comment
@codeymodey Пожалуйста, не запутайтесь. stdin, stdout, stderr относятся к типу FILE *, которые относятся соответственно к стандартному потоку ввода (обычно клавиатура), стандартному потоку вывода (обычно дисплейный терминал), стандартному поток ошибок (часто дисплейный терминал). - person ajay; 27.02.2014

Да, по умолчанию стандартный вывод буферизуется строкой при подключении к терминалу. Буфером управляет операционная система, обычно вам не нужно об этом беспокоиться.

Вы можете изменить это поведение, используя setbuf() или setvbuf(), например, чтобы изменить его на отсутствие буфера:

setbuf(stdout, NULL);

Все функции printf, puts, putchar выводят на стандартный вывод, поэтому используют один и тот же буфер.

person Yu Hao    schedule 27.02.2014
comment
См. ответ Devolus выше, он говорит, что стандартный вывод реализован в библиотеке C. Вы, ребята, одинаковы в этом вопросе или разные? - person codey modey; 27.02.2014

Если вы хотите, вы можете сбросить символы перед новой строкой, вызвав

fflush(stdout);

Это может быть удобно, если вы медленно печатаете что-то вроде индикатора выполнения, где каждый символ печатается без новой строки.

int main()
{
    printf("Hello"); // Doesn't display anything on the screen
    fflush(stdout);  // Now, hello appears on the screen
    printf("\n");    // The new line gets printed
    return 0;
}
person Chris McGrath    schedule 27.02.2014