cstdio streams vs iostream streams?

cstdio streams vs iostream streams?

本文关键字:streams iostream vs cstdio      更新时间:2023-10-16

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

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

我对此很困惑? 有人可以澄清一下C++的iostream和C的stdio是不同的东西,它们做完全相同的事情,只是在不同的抽象级别上?我以为他们是一回事!?

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

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

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

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

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

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

好的,这是我发现的。

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

现在,以Microsoft Windows为例。实际上有可用于STDINSTDIO等的句柄(见这里(。所以基本上,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 句柄与(例如(标准输出相关联时,它对另一层 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等使用句柄而不是整数的系统兼容。

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

它们是同一件事,但它们也可能是分开缓冲的。这可能会影响混合使用 C 和 C++ I/O 的代码,如下所示

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

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

因此,该标准允许您调用std::sync_with_stdio(false)说您不关心这样的代码,但如果它有所作为,则更愿意让标准流尽快工作。在许多系统上,它没有区别。

一个可以是另一个的包装器(这是双向的。您可以使用iostream实现stdio函数,反之亦然。或者您可以完全独立编写它们。

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

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