потоки cstdio против потоков iostream? - PullRequest
30 голосов
/ 11 марта 2012

Я только что узнал о существовании функции ios_base::sync_with_stdio, которая в основном позволяет отключить (или включить, если вы уже выключили) синхронизацию между iostream потоками, которые используются в C ++ и cstdio потоки, которые являются частью стандарта C.

Теперь я всегда думал, что stdout, stderr и stdin в C были по существу обернуты в набор объектов в C ++ в классах iostreams. Но если они должны быть синхронизированы друг с другом, это будет означать, что iostream классы C ++ являются , а не оболочкой вокруг C stdin и т. Д.

Меня это смущает? Может кто-нибудь прояснить, как iostream C ++ и stdio C * разные вещи, которые делают одно и то же, просто на другом уровне абстракции? Я думал, что они то же самое !?

Как получается, что они должны быть синхронизированы? Я всегда думал, что это одно и то же, одно обертывание другого, по сути.

Ответы [ 5 ]

34 голосов
/ 11 марта 2012

Стандарты C и C ++ не предъявляют требований к тому, как все реализовано, а только к тому, каков эффект определенных операций.Для функциональности <stdio> против <iostream> это означает, что один может обернуть другой, оба могут быть по существу одинаковыми или что они либо полностью независимы.Технически, использование общей реализации было бы идеальным по нескольким причинам (например, не было бы необходимости в явной синхронизации и был бы определенный механизм для расширения FILE* для пользовательских систем), но я не знаю ни одной системы, которая на самом делеЯвляется ли это.Возможно иметь одну реализацию как оболочку другой, и реализация <iostream> s в терминах <stdio> была типичным выбором реализации, хотя у нее есть недостаток, заключающийся в том, что она требует дополнительных затрат для определенных операций, и большинство стандартных библиотек C ++ перешли наиспользовать полностью отдельные реализации.

К сожалению, как у обернутой, так и у независимой реализации есть общая проблема: ввод / вывод ужасно неэффективен, когда выполняется один символьный уровень.Таким образом, по существу, необходимо буферизовать символы и выполнять чтение или запись в буфер.Это хорошо работает для потоков, которые не зависят друг от друга.Подвох - это стандартные C-потоки stdin, stdout, stderr и их узкие символы C ++ std::cin, std::cout, std::cerr / std::clog и аналоги широких символов C ++ std::wcin, std::wcout, std::wcerr / std::wclog соответственно: что происходит, когда пользователь читает как из stdin, так и std::cin?Если какой-либо из этих потоков читает буфер символов из базового потока ОС, чтения будут отображаться не по порядку.Точно так же, если бы оба stdout и std::cout использовали независимые буферы, символы появлялись бы в неожиданном порядке, когда пользователь записывает оба потока.В результате существуют специальные правила для стандартных потоковых объектов C ++ (т. Е. std::cin, std::cout, std::cerr и std::clog и их аналоги с широкими символами), которые предписывают синхронизировать их с соответствующими аналогами <stdio>,По сути, это означает, что конкретно эти объекты C ++ либо напрямую используют общую реализацию, либо они реализованы в терминах <stdio> и без буферизации каких-либо символов.

Было понято, чтостоимость такой синхронизации весьма существенна, если реализации не разделяют общую базу и могут быть ненужными для некоторых пользователей: если пользователь использует только <iostream>, он не хочет платить за дополнительное косвенное обращение и, что более важно,он не хочет платить за дополнительные расходы, связанные с отсутствием буфера.Для осторожных реализаций стоимость неиспользования буфера может быть весьма существенной, потому что это означает, что определенные операции в конечном итоге должны выполнять проверку и, возможно, вызов виртуальной функции в каждой итерации, а не только время от времени.Таким образом, std::sync_with_stdio() может использоваться для отключения этой синхронизации, что может означать, что стандартные объекты потока более или менее полностью изменяют свою внутреннюю реализацию.Поскольку потоковые буферы стандартных потоковых объектов могут быть заменены пользователем, к сожалению, потоковые буферы не могут быть заменены, но внутренняя реализация потокового буфера может быть изменена.

В хороших реализациях *Библиотека 1037 * все это влияет только на стандартные объекты потока.То есть файловые потоки не должны быть полностью затронуты этим.Однако если вы хотите использовать стандартные потоковые объекты и хотите добиться хорошей производительности, вы явно не хотите смешивать <stdio> и <iostream> и хотите отключить синхронизацию.Особенно при сравнении производительности ввода / вывода между <stdio> и <iostream> вы должны знать об этом.

3 голосов
/ 11 марта 2012

На самом деле stdout, stderr и stdin являются обработчиками файлов ОС.И FILE структура C, а также iostream классы C ++ являются оболочками этих обработчиков файлов.И классы iostream, и структура FILE могут иметь свои собственные буферы или что-то еще, что необходимо синхронизировать между собой, чтобы убедиться, что ввод из файла или вывод в файл выполняется правильно.

2 голосов
/ 11 марта 2012

Хорошо, вот что я нашел.

На самом деле, ввод / вывод в конечном счете выполняется собственными системными вызовами и функциями.

Теперь возьмем, например, Microsoft Windows. На самом деле доступны ручки для STDIN, STDIO и т. Д. (См. здесь ). Таким образом, в основном, C ++ iostream и C stdio вызывают собственные системные функции, C ++ iostream не включает функции ввода / вывода C (в современных реализациях). Он вызывает собственные системные методы напрямую.

Также я нашел это:

После перенаправления stdin, stdout и stderr можно использовать стандартные функции C, такие как printf () и gets (), без изменений, для связи с консолью Win32. Но как насчет потоков ввода-вывода C ++? Поскольку cin, cout, cerr и clog тесно связаны с stdin, stdout и stderr в C, можно ожидать, что они будут вести себя аналогично. Это наполовину верно.

Потоки ввода-вывода C ++ на самом деле бывают двух видов: шаблонные и не шаблонные. Старая не шаблонная версия потоков ввода / вывода постепенно заменяется новым стилем шаблонов потоков, которые сначала были определены Стандартной библиотекой шаблонов (STL) и которые теперь включаются в стандарт ANSI C ++. Visual C ++ v5 предоставляет оба типа и позволяет выбирать между ними, включая различные заголовочные файлы. Потоки ввода / вывода STL работают, как и следовало ожидать, автоматически с использованием любых недавно перенаправленных дескрипторов stdio. Тем не менее, не шаблонные потоки ввода / вывода не работают должным образом. Чтобы выяснить, почему, я посмотрел исходный код, удобно предоставленный на компакт-диске Visual C ++.

Проблема в том, что старые потоки ввода / вывода были разработаны для использования «файловых дескрипторов» в стиле UNIX, где вместо дескрипторов используются целые числа (0 для стандартного ввода, 1 для стандартного вывода и т. Д.). Это удобно для реализаций UNIX, но компиляторы Win32 C должны предоставить еще один уровень ввода-вывода для представления этого стиля ввода-вывода, поскольку Win32 не обеспечивает совместимый набор функций. В любом случае, когда вы вызываете _open_osfhandle (), чтобы связать новый дескриптор Win32 с (например) stdout, это не влияет на другой уровень кода ввода / вывода. Следовательно, файловый дескриптор 1 продолжит использовать тот же базовый дескриптор Win32, что и раньше, и отправка вывода в cout не даст желаемого эффекта.

К счастью, разработчики оригинального пакета потоков ввода / вывода предвидели эту проблему и предоставили чистое и полезное решение. Базовый класс ios предоставляет статическую функцию sync_with_stdio (), которая заставляет библиотеку изменять свои базовые файловые дескрипторы, чтобы отразить любые изменения в стандартном уровне ввода-вывода. Хотя это не является строго необходимым для потоков ввода-вывода STL, это не причиняет вреда и позволяет мне писать код, который корректно работает с новой или старой формой потоков ввода-вывода.

( источник )

Следовательно, вызов sync_with_stdio() фактически изменяет базовые файловые дескрипторы. Фактически, разработчики добавили его для обеспечения совместимости старого ввода-вывода C ++ с такими системами, как Windows-32, в которых вместо целых чисел использовались дескрипторы.

Обратите внимание, что использование sync_with_stdio() не является необходимым с современным вводом / выводом STL на основе шаблонов C ++.

1 голос
/ 11 марта 2012

Они - это одно и то же, но они также могут буферизоваться отдельно. Это может повлиять на код, который смешивает использование ввода-вывода C и C ++, например

std::cout << "Hello ";
printf("%s", "world");
std::cout << "!\n";

Чтобы это работало, базовые потоки должны быть как-то синхронизированы. В некоторых системах это может означать, что производительность может пострадать.

Таким образом, стандарт позволяет вам позвонить std::sync_with_stdio(false) и сказать, что вам не нравится подобный код, но вы бы предпочли, чтобы стандартные потоки работали как можно быстрее , если это имеет значение . Во многих системах это не имеет значения.

1 голос
/ 11 марта 2012

Один может быть оберткой для другого (и это работает в обоих направлениях. Вы можете реализовать stdio функции с помощью iostream и наоборот. Или вы можете написать они совершенно независимо.

И sync_with_stdio гарантирует, что два потока будут синхронизированы, если он включен. Но они все равно могут синхронизироваться, когда он тоже отключен, если действительно хочет.

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

...