30

我刚刚了解到该ios_base::sync_with_stdio函数的存在,它基本上允许您关闭(或者如果您已经关闭它,则打开)iostreamC++ 中使用的cstdio流与标准 C 中的流之间的同步。

现在,我一直认为stdout, stderrand stdinin C 本质上是包装在 iostreams 类中的一组 C++ 对象中。但是如果它们必须相互同步,这将表明 C++ 的iostream不是stdinC等 的包装器 。

我对此感到很困惑?有人可以澄清 C++ 的 iostream 和 C 的 stdio 是如何做完全相同的事情的不同事物,只是在不同的抽象级别上?我以为他们是一样的!?

它们必须如何同步?我一直认为它们是同一个东西,本质上是一个包裹另一个。

4

5 回答 5

38

C 和 C++ 标准对事物的实现方式没有任何要求,只是对某些操作的效果有什么要求。对于<stdio>vs.<iostream>功能,这意味着一个可以包装另一个,两者可以本质上相同,或者它们是完全独立的。从技术上讲,出于多种原因,使用通用实现将是理想的(例如,不需要显式同步,并且将有一个定义的机制来扩展FILE*用户定义的系统),但我不知道有任何系统实际上会这样做。让一种实现成为另一种实现的包装是可能的,并根据以下方式实现<iostream>s<stdio>是一个典型的实现选择,尽管它的缺点是它为某些操作引入了额外的成本,并且大多数 C++ 标准库已经转向使用完全独立的实现。

不幸的是,封装实现和独立实现都有一个共同的问题:当完成一个字符级别时,I/O 效率极低。因此,缓冲字符并从缓冲区读取或写入缓冲区基本上是强制性的。这对于彼此独立的流非常有效。问题分别是标准 C 流stdin,stdoutstderr它们的 C++ 窄字符对应物std::cin, std::cout, std::cerr/std::clog和 C++ 宽字符对应物std::wcin, std::wcout, std::wcerr/ std::wclog:当用户同时读取stdin和时会发生什么std::cin?如果这些流中的任何一个从底层 OS 流中读取字符缓冲区,则读取将出现乱序。同样,如果两者stdoutstd::cout当用户同时写入两个流时,使用的独立缓冲区字符将以意外的顺序出现。因此,标准 C++ 流对象(即std::cinstd::coutstd::cerrstd::clog它们的宽字符对应物)有一些特殊规则,要求它们与各自的<stdio>对应物同步。实际上,这意味着这些 C++ 对象要么直接使用通用实现,要么根据<stdio> 缓冲任何字符来实现。

人们意识到,如果实现不共享一个共同的基础,这种同步的成本是相当可观的,并且对于某些用户来说可能是不必要的:如果用户只使用<iostream>他不想为额外的间接付费,更重要的是,他不想支付不使用缓冲区所带来的额外成本。对于谨慎的实现,不使用缓冲区的成本可能相当高,因为这意味着某些操作最终必须在每次迭代中进行检查,并且可能需要进行虚函数调用,而不是偶尔进行一次。因此,std::sync_with_stdio()可用于关闭此同步,这可能意味着标准流对象或多或少完全改变了它们的内部实现。由于标准流对象的流缓冲区可以由用户替换,不幸的是,流缓冲区不能被替换,但流缓冲区的内部实现可以改变。

<iostream>库的良好实现中,所有这些仅影响标准流对象。也就是说,文件流应该完全不受此影响。但是,如果您想使用标准流对象并希望获得良好的性能,您显然不想混合<stdio>并且<iostream>想要关闭同步。特别是在比较 和 之间的 I/O 性能时<stdio><iostream>您应该意识到这一点。

于 2012-03-11T10:30:46.297 回答
3

实际上 stdout,stderrstdin是操作系统的文件处理程序。FILEC 的结构以及C iostream++ 的类都是这些文件处理程序的包装器。iostream 类和 FILE 结构都可能有自己的缓冲区或其他需要相互同步的东西,以确保文件的输入或文件的输出正确完成。

于 2012-03-11T09:25:04.823 回答
2

好的,这就是我找到的。

实际上,I/O 最终由本地系统调用和函数执行。

现在,以 Microsoft Windows 为例。等实际上有可用的句柄(请参见STDIN此处。所以基本上,C++和 C调用本机系统函数,C++不包装 C 的 I/O 函数(在现代实现中)。它直接调用本机系统方法。STDIOiostreamstdioiostream

另外,我发现了这个:

一旦 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 样式的“文件描述符”,其中使用整数而不是句柄(0 表示标准输入,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 流一起正常工作。

来源

因此调用sync_with_stdio()实际上改变了底层文件描述符。它实际上是由设计者添加的,以确保旧的 C++ I/O 与使用句柄而不是整数的 Windows-32 等系统兼容。

请注意,sync_with_stdio()现代基于 C++ 模板的 STL I/O 不需要 Using。

于 2012-03-11T09:33:54.847 回答
1

一个可以是另一个的包装器(这两种方式都可以。您可以通过 using 来实现stdio功能,iostream反之亦然。或者您可以完全独立地编写它们。

sync_with_stdio保证如果启用,两个流将同步。但是如果真的想要的话,他们仍然可以在它被禁用时进行同步。

但是,即使一个是另一个的包装器,例如,一个可能仍然有另一个不共享的缓冲区,因此仍然需要同步。

于 2012-03-11T09:38:17.250 回答
1

They are the same thing, but they might also be buffered separarately. This could affect code that mixes the use of C and C++ I/O, like this

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

For this to work, the underlying streams must be synchronized somehow. On some systems, this might mean that performance could suffer.

So, the standard allows you to call std::sync_with_stdio(false) to say that you don't care about code like this, but would prefer to have the standard streams work as fast as possible if it makes a difference. On many systems it doesn't make a difference.

于 2012-03-11T10:30:55.947 回答