2012-03-11 3 views
29

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

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

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

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

+4

+1, woah Я всегда думал, что C++ 'iostream' был оберткой C' stdio'. – ApprenticeHacker

ответ

33

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

К сожалению, как обернутая, так и независимая реализация имеют общую проблему: I/O является ужасно неэффективным, когда выполняется один уровень персонажа. Таким образом, по существу, необходимо буферизовать символы и читать или записывать в буфер. Это хорошо работает для потоков, которые независимы друг от друга.Загвоздка являются стандарт 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() может использоваться для выключения этой синхронизации, что может означать, что стандартные потоковые объекты более или менее полностью изменяют свою внутреннюю реализацию. Поскольку потоковые буферы стандартных объектов потока могут быть заменены пользователем, к сожалению, буферы потока не могут быть заменены, но внутренняя реализация буфера потока может быть изменена.

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

+0

+1, отличное объяснение. – Xeo

+0

Не удалось найти лучшего объяснения сценария. Однажды я вступил в эту ловушку и провел 3 дня, чтобы понять, что произошло. Отличная работа, Дитмар! –

3

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

+0

'stdin' и друзья - это структуры типа' FILE' и 'C'. У os просто есть дескрипторы файлов '0',' 1' и '2', как определено в' unistd.h' как '#define STDIN_FILENO 0'. – hochl

+0

правильный.Но также stdin, stdout и т. Д. Являются аббревиатурами «стандартный поток ввода», «стандартный выходной поток» и т. Д. Соответственно. Я использовал эти слова как аббревиатуры для системных потоков. – Jurlie

2

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

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

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

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

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

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

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

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

(source)

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

Отметьте, что использование современных приложений на основе шаблонов STL на базе шаблонов STL не требуется с использованием sync_with_stdio().

+1

Возможно, вам стоит отметить, что ваш источник говорит о «Visual C++ 5» и «CD-ROM», просто чтобы выразить это в контексте. –

+0

@ KerrekSB это потому, что он был написан в 90-х годах. :). Когда STL была «новой вещью». – ApprenticeHacker

1

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

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

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

+0

Но в соответствии с ссылкой, которую я опубликовал в своем ответе, автор говорит о более ранних вводах-выводах на основе UNIX, которые использовали целые числа вместо дескрипторов для дескрипторов файлов, что вызвало несовместимость с Windows, поэтому разработчики добавили 'sync_with_stdio'. Разве это не проблема совместимости? – ApprenticeHacker

+0

Это не имеет никакого отношения к unix-style io (файловые дескрипторы), которые никогда не будут синхронизироваться с iostream, если вы полностью не отключите буферизацию. Он относится только к уровню stdio ('FILE'). –

1

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

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

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

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