Подтвердить что ты не робот

Потоки cstdio против потоков iostream?

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

Ответ 3

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

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

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

Кроме того, я нашел это:

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

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

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

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

(источник)

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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