Буферизованный поток ввода-вывода C++

Я понимаю, что по умолчанию все потоковые операции ввода-вывода, поддерживаемые C++, буферизуются.

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

Но как проверить это поведение в действии. Я имею в виду следующий код

int main()
{
    cout << "Hello world\n";
    return 0
}

При чем тут буферизация? Я знаю, что происходит буферизация, но как это объяснить? Вывод сразу виден на экране, так что же может быть примером кода, чтобы действительно увидеть буферный ввод-вывод в действии?


person Arun    schedule 09.07.2012    source источник
comment
В случае cout размер буфера составляет 1 байт. Настоящая буферизация вступает в игру, когда вы читаете/записываете файлы.   -  person Mr.Anubis    schedule 09.07.2012
comment
@Mr.Anubis Как вы это понимаете? И нет, это не обязательно правда.   -  person Konrad Rudolph    schedule 09.07.2012
comment
@KonradRudolph Ну, я не помню источник этого высказывания, но в большинстве случаев это происходит   -  person Mr.Anubis    schedule 09.07.2012
comment
@Mr.Anubis В стандартных потоках нет случаев, когда используется буфер размером 1 байт. std::cout буферизуется нормально. (Это отличается от stdout в C, который буферизирует строки, если он подключен к интерактивному устройству, и полностью буферизируется в противном случае. В C++ нет концепции буферизации строк; использование std::endl эффективно имитирует ее. .)   -  person James Kanze    schedule 09.07.2012


Ответы (3)


Попробуйте следующую программу. sleep(1) используется для введения задержки (1 секунда), я использую Linux, поэтому sleep у меня работает. Если вы не можете заставить это работать, попробуйте другие способы задержки этой программы (например, простой цикл for). Вы также можете попробовать увеличить размер буфера (раскомментировать закомментированные строки кода), если вы не видите никакого эффекта буферизации.

В моей ОС (Linux 3.2.0) и компиляторе (g++ 4.6.3) эта программа печатает «Порция1Порция2», затем «Порция3Порция4», а затем «Порция5». std::endl гарантированно очищает буфер, но, как видите, перевод строки характер также работает таким образом для меня.

#include <iostream>
#include <unistd.h>

using namespace std;

int main () {
    // Try uncommenting following lines to increase buffer size
    // char mybuf[1024];
    // cout.rdbuf()->pubsetbuf(mybuf, 1024);

    cout << "Portion1";
    sleep(1);
    cout << "Portion2\n";
    sleep(1);
    cout << "Portion3";
    sleep(1);
    cout << "Portion4" << endl;
    sleep(1);
    cout << "Portion5" << endl;
    sleep(1);
    cout << "Done!" << endl;

    return 0;
}
person Alexander Putilin    schedule 09.07.2012
comment
Даже когда строки раскомментированы? - person Alexander Putilin; 09.07.2012

Во-первых, не весь iostream буферизуется; буферизация обрабатывается прикрепленным файлом streambuf. В случае filebuf (используется ifstream и ofstream) ввод будет считывать столько, сколько возможно, вплоть до размера буфера, а вывод будет сбрасывать буфер при переполнении, когда происходит явный сброс или закрытие, или когда объект уничтожается (что неявно вызывает close).

Случай cout немного особенный, так как он никогда не уничтожается и не закрывается. Есть гарантия от системы, что flush будет вызвана на ней хотя бы один раз после вызова exit (что и происходит при возврате из main). Это означает, что любой вывод перед возвратом из main будет сброшен; если вы используете cout в деструкторах статических объектов, вам все равно нужен явный сброс, чтобы быть уверенным.

Также возможно tie преобразовать поток вывода во входной поток; cout привязан к cin по умолчанию. В этом случае любая попытка ввода из связанного потока приведет к сбросу вывода.

Обычно принято просто использовать std::endl вместо простого вывода '\n'; std::endl выводит '\n', а затем сбрасывает поток. Для потоков, где очень важно, чтобы весь вывод отображался быстро, можно установить флаг unitbuf, который означает, что поток будет сбрасываться в конце каждого оператора <<. (У std::cerr это установлено по умолчанию.)

Наконец, если вы хотите увидеть эффект буферизации, поставьте после вывода что-то вроде sleep(10). Если его вывод появляется немедленно, он был сброшен; если это не так, он был буферизован, и сброс произошел неявно после sleep.

person James Kanze    schedule 09.07.2012
comment
Спасибо за ответ. Но было бы здорово, если бы вы предоставили мне пример кода, который стабильно работает. - person Arun; 09.07.2012
comment
@Arun Который постоянно работает для чего? Использование std::endl вместо '\n' вызовет сброс в конце каждой строки. Если вам нужно больше, std::cout << std::flush безоговорочно сбрасывает вывод. - person James Kanze; 09.07.2012
comment
Мне нужен пример, который изображает роль буфера. Что-то вроде cout ‹‹ Hello world; //Здесь вывод не отображается на консоли cout ‹‹ endl; // Теперь Hello world виден на консоли. - person Arun; 09.07.2012
comment
@Arun Так что поместите задержку между этими двумя операторами, и вы должны увидеть, что ни один вывод не происходит до тех пор, пока не будет задержки. - person James Kanze; 09.07.2012
comment
Пробовал это безуспешно на VS2008 и VS2010. Утверждения немедленно выводятся на экран. Таким образом, никаких признаков буферизации не наблюдается. - person Arun; 10.07.2012
comment
@Arunm Так что, возможно, VC2010 использует очень маленький буфер. Или изменяет свою политику буферизации, если устройство вывода является интерактивным. (Или, что я думаю, что компилятор в конечном итоге выполняет буферизацию модулей из-за того, как он реализует синхронизацию со stdio.) - person James Kanze; 10.07.2012

Попробуйте следующий код:

int main()
{
    for( int i =0 ; i < 10; i ++ )
    {
        cout << i << " ";
        cerr << i << " ";
    }
}

Буферизованный вывод обычно очищается при уничтожении объекта потока, поэтому приведенный выше код будет печатать (не всегда, конечно, но для меня это работает с gcc 4.6.3)

0 1 2 3..9
0 1 2 3..9

вместо

0 0 1 1 2 2 3 3 .... 9 9 

мой вывод

Потому что небуферизованное cerr печатается сразу (первая последовательность), а буферизованное cout печатается в конце main().

person SingerOfTheFall    schedule 09.07.2012
comment
@Арун, вот почему я сказал не всегда. Буферизация зависит от реализации, поэтому вы увидите разные результаты с разными компиляторами и настройками. Иногда выходы могут даже смешиваться в странном порядке. - person SingerOfTheFall; 09.07.2012
comment
std::cout никогда не уничтожается, но буферы могут очищаться по другим причинам. В этом случае и std::cin, и std::cerr привязаны к std::cout, что означает, что любой ввод-вывод на одном из них будет сбрасывать std::cout. И std::cerr буферизуется по единицам, что означает, что каждый оператор << сбрасывает его в конце операции. - person James Kanze; 09.07.2012
comment
@SingerOfTheFall нет ли последовательного примера, который будет работать всегда? Я пробовал все примеры в вопросе, ни один из них не работает ... означает ли это, что для VS2010 он всегда очищает буферы? - person Arun; 09.07.2012
comment
@ Арун, извини, я не знаю последовательного примера. Я разместил изображение моего вывода, проверьте ответ, пожалуйста. - person SingerOfTheFall; 09.07.2012
comment
@SingerOfTheFall Это может (вероятно) зависеть от версии библиотеки. C++11 требует, чтобы std::cout был привязан к std::cerr (в дополнение к std::cin); это означает, что любой вывод в std::cerr сбрасывает std::cout, давая 0 0 1 1 .... С++ 03 не позволяет связывать его, поэтому обычно выдает 0 1 2... 0 1 2.... - person James Kanze; 09.07.2012
comment
@JamesKanze, да, это звучит правдоподобно. Возможно, это также зависит от настроек оптимизации. - person SingerOfTheFall; 09.07.2012
comment
@SingerOfTheFall Оптимизация не имеет ничего общего с семантикой, реализованной в библиотеке. Стандарт определенно требует другого поведения (относительно ничьей) здесь. Реализация C++03 могла сбрасывать данные, поскольку использовала, скажем, 3-байтовый буфер, но это не зависело бы от любого вывода на std::cerr. Реализация C++11 должна очищаться независимо от размера буфера, как только что-либо выводится в std::cerr. - person James Kanze; 09.07.2012