我刚刚了解到ios_base::sync_with_stdio
函数的存在,它基本上允许你在C ++中使用的iostream
流之间关闭(或者如果已经关闭它)。作为标准C的一部分的cstdio
流。
现在,我一直认为C中的stdout
,stderr
和stdin
基本上包含在iostreams类的C ++中的一组对象中。但如果它们必须彼此同步,这将表明C ++的iostream
类不围绕C stdin
等的包装器。 / p>
我对此很困惑?有人可以澄清C ++的iostream和C的stdio是如何不同的完全相同的东西,只是在不同的抽象层次上?我以为他们是同样的东西!?
它们是如何同步的?我一直以为它们是同一个东西,一个包装另一个,基本上。
答案 0 :(得分:34)
C和C ++标准对事物的实现方式没有要求,只是对某些操作的影响是什么。对于<stdio>
与<iostream>
功能,这意味着可以包装另一个,两者可以基本相同,或者它们完全独立。从技术上讲,使用通用实现是理想的有几个原因(例如,不需要显式同步,并且会有一个定义的机制来扩展FILE*
用户定义的系统)但我不知道任何系统这实际上是这样的。让一个实现成为另一个实现的包装是可能的,并且<iostream>
实现<stdio>
是一个典型的实现选择,尽管它的缺点是它为某些操作和大多数C ++标准引入了额外的成本图书馆已经开始使用完全独立的实现。
不幸的是,包装和独立实现都有一个共同的问题:当完成一个字符级别时,I / O非常低效。因此,基本上必须缓冲字符并读取或写入缓冲区。这适用于彼此独立的流。捕获的是标准C流stdin
,stdout
,stderr
及其C ++窄字符对等std::cin
,std::cout
,std::cerr
/ {{ 1}}和C ++宽字符对应std::clog
,std::wcin
,std::wcout
/ std::wcerr
:当用户同时从std::wclog
和{{{ 1}?如果这些流中的任何一个从底层OS流中读取字符缓冲区,则读取将无序地出现。同样,如果stdin
和std::cin
使用独立缓冲区,则当用户同时向两个流写入时,字符将以意外顺序出现。因此,对标准C ++流对象(即stdout
,std::cout
,std::cin
和std::cout
及其广泛字符对应项)有特殊规则,要求它们与其各自的std::cerr
对应方同步。实际上,这意味着特别是这些C ++对象要么直接使用通用实现,要么用std::clog
和实现,不要缓冲任何字符。
我们意识到,如果这些实现不共享一个共同的基础并且对于某些用户来说可能是不必要的,那么这种同步的成本是相当大的:如果用户只使用<stdio>
他不想支付对于额外的间接,更重要的是,他不想支付因不使用缓冲而产生的额外费用。对于仔细的实现,不使用缓冲区的成本可能非常大,因为这意味着某些操作最终必须在每次迭代中进行检查并可能进行虚拟函数调用,而不是偶尔进行一次。因此,<stdio>
可用于关闭此同步,这可能意味着标准流对象或多或少完全改变其内部实现。由于标准流对象的流缓冲区可以由用户替换,遗憾的是,流缓冲区无法替换,但流缓冲区的内部实现可以更改。
在<iostream>
库的良好实现中,所有这些仅影响标准流对象。也就是说,文件流应该完全不受此影响。但是,如果您想使用标准流对象并希望获得良好的性能,您显然不希望混合std::sync_with_stdio()
和<iostream>
,并且您希望关闭同步。特别是,在比较<stdio>
和<iostream>
之间的I / O性能时,您应该注意这一点。
答案 1 :(得分:3)
实际上stdout
,stderr
和stdin
是操作系统的文件处理程序。 C {的FILE
结构以及C ++的iostream
类都是这些文件处理程序的包装器。 iostream类和FILE结构都可能有自己的缓冲区或需要在彼此之间同步的其他东西,以确保从文件或输出到文件的输入正确完成。
答案 2 :(得分:2)
好的,这是我发现的。
实际上,I / O最终由本机系统调用和函数执行。
现在,以Microsoft Windows为例。实际上有STDIN
,STDIO
等句柄(请参阅here)。基本上,C ++ iostream
和C stdio
都调用本机系统函数,C ++ iostream
不包装C的I / O函数(在现代实现中)。它直接调用本机系统方法。
另外,我发现了这个:
一旦stdin,stdout和stderr被重定向,可以使用标准C函数(如printf()和gets(),无需更改即可与Win32控制台通信。但是C ++ I / O流呢?由于cin,cout,cerr和clog与C的stdin,stdout和stderr紧密相关,你会发现它们的行为类似。这是对的一半。
C ++ I / O流实际上有两种形式:模板和非模板。 I / O流的旧的非模板版本正逐渐被标准模板库(STL)首先定义的更新的模板样式流替换,现在它们被吸收到ANSI C ++标准中。 Visual C ++ v5提供了这两种类型,并允许您通过包含不同的头文件来在两者之间进行选择。 STL I / O流按预期工作,自动使用任何新重定向的stdio句柄。但是,非模板I / O流不能按预期工作。为了解释原因,我查看了Visual C ++ CD-ROM上方便提供的源代码。
问题在于旧的I / O流被设计为使用UNIX样式的“文件描述符”,其中使用整数而不是句柄(stdin为0,stdout为1,依此类推)。这对于UNIX实现来说很方便,但是Win32 C编译器必须提供另一个I / O层来表示这种I / O样式,因为Win32不提供兼容的函数集。在任何情况下,当您调用_open_osfhandle()将新的Win32句柄与(例如)stdout相关联时,它对其他I / O代码层没有影响。因此,文件描述符1将继续使用与之前相同的基础Win32句柄,并且将输出发送到cout将不会产生所需的效果。
幸运的是,原始I / O流包的设计者预见到了这个问题,并提供了一个干净而有用的解决方案。基类ios提供静态函数sync_with_stdio(),该函数使库更改其基础文件描述符以反映标准I / O层中的任何更改。虽然这对于STL I / O流并不是绝对必要的,但它并没有什么坏处,让我编写的代码能够与新的或旧的I / O流形式一起正常工作。
(source)
因此,调用sync_with_stdio()
实际上会更改基础文件描述符。实际上,设计人员已经添加了它来确保旧的C ++ I / O与使用句柄而不是整数的Windows-32等系统的兼容性。
请注意,对于基于现代C ++模板的STL I / O,不需要使用sync_with_stdio()
。
答案 3 :(得分:1)
一个可以成为另一个的包装器(并且可以双向工作。使用stdio
可以 实现iostream
函数或者。或者你可以完全独立地写它们。
sync_with_stdio
保证两个流在启用时将同步。但是,如果他们真的想要,他们仍然可以同步。
但是即使一个是另一个的包装器,例如,一个人可能仍然有另一个不共享的缓冲区,因此仍然需要同步。
答案 4 :(得分:1)
他们是相同的东西,但它们也可能是单独缓冲的。这可能会影响混合使用C和C ++ I / O的代码,例如
std::cout << "Hello ";
printf("%s", "world");
std::cout << "!\n";
为此,必须以某种方式同步基础流。在某些系统上,此可能意味着性能可能会受到影响。
因此,该标准允许您致电std::sync_with_stdio(false)
表示您不关心此类代码,但更愿意让标准流尽可能快地运行如果它有所作为。在许多系统上,它没有什么区别。