【问题标题】:cstdio streams vs iostream streams?cstdio 流与 iostream 流?
【发布时间】:2012-03-28 01:35:38
【问题描述】:

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

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

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

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

【问题讨论】:

  • +1,哇,我一直认为 C++ iostream 也是 C stdio 的包装器。

标签: c++ c stream iostream cstdio


【解决方案1】:

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

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

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

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

【讨论】:

  • 找不到对该场景的更好解释。我曾经踏入这个陷阱,花了 3 天时间才明​​白发生了什么。干得好,Dietmar!
【解决方案2】:

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

【讨论】:

  • stdin 和朋友是 FILEC 类型的结构。操作系统只有文件描述符012,在unistd.h 中定义为#define STDIN_FILENO 0
  • 正确。而且标准输入、标准输出等分别是“标准输入流”、“标准输出流”等的缩写。我用这些词作为系统流的缩写。
【解决方案3】:

好的,这就是我找到的。

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

现在,以 Microsoft Windows 为例。 STDINSTDIO 等实际上有可用的句柄(请参阅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 样式的“文件描述符”,其中使用整数而不是句柄(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 流一起正常工作的代码。

(source)

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

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

【讨论】:

  • 值得注意的是,您的消息来源谈到了“Visual C++ 5”和“CD-ROM”,只是为了将其放在上下文中。
  • @KerrekSB 那是因为它是在 90 年代编写的。 :) 。当 STL 成为“新事物”时。
【解决方案4】:

一个可以成为另一个的包装器(这两种方式都有效。您可以通过使用iostream 来实现stdio 函数,反之亦然。或者您可以完全独立编写。

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

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

【讨论】:

  • 但是根据我在回答中发布的参考资料,作者说旧的基于 UNIX 的 I/O,它使用整数而不是文件描述符的句柄,这导致与 Windows 不兼容,所以设计师添加了sync_with_stdio。这不是更多的兼容性问题吗?
  • 它与unix风格的io(文件描述符)无关,除非你完全禁用缓冲,否则它永远不会与iostream同步。它仅适用于 stdio(FILE 级别)。
【解决方案5】:

它们相同的东西,但它们也可能被单独缓冲。这可能会影响混合使用 C 和 C++ I/O 的代码,像这样

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

为此,必须以某种方式同步底层流。在某些系统上,这可能意味着性能可能会受到影响。

因此,标准允许您致电 std::sync_with_stdio(false) 表示您不关心这样的代码,但希望标准流尽可能快地工作如果它有所作为时间>。在许多系统上它没有任何区别。

【讨论】:

    猜你喜欢
    • 2016-12-31
    • 1970-01-01
    • 2011-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-16
    • 1970-01-01
    相关资源
    最近更新 更多