【问题标题】:How is `std::cout` implemented?`std::cout` 是如何实现的?
【发布时间】:2022-12-19 22:33:07
【问题描述】:

std::coutstd::ostream 的一个实例。我可以在名为 /usr/include/c++/7/iostream 的文件中看到 std::cout 的声明:

extern ostream cout;      /// Linked to standard output

std::ostream是由typedef std::basic_ostream<char> std::ostream定义的。

更何况,你好像不能创建std::ostream的实例。请参阅此演示code snippet

#include<iostream>

int main()
{
    std::ostream os;
    return 0;
}

以下是编译器对上面代码 sn-p 的抱怨:

In file included from /opt/compiler-explorer/gcc-4.9.0/include/c++/4.9.0/iostream:39:0,
                 from <source>:1:
/opt/compiler-explorer/gcc-4.9.0/include/c++/4.9.0/ostream: In function 'int main()':
/opt/compiler-explorer/gcc-4.9.0/include/c++/4.9.0/ostream:384:7: error: 'std::basic_ostream<_CharT, _Traits>::basic_ostream() [with _CharT = char; _Traits = std::char_traits<char>]' is protected
       basic_ostream()
       ^
<source>:5:18: error: within this context
     std::ostream os;
                  ^

问题来了,既然 std::basic_ostream&lt;_CharT, _Traits&gt;::basic_ostream() 被标记为受保护,那么 std::cout 是如何创建的呢?

CppReference 上的这个link 好像意义不大。并没有明确告诉我std::cout是如何实现的,以及std::ostream的构造函数是如何创建std::cout的。据我所知,最相关的信息是:

全局对象 std::coutstd::wcout 控制输出到实现定义类型的流缓冲区(派生自 std::streambuf),与标准 C 输出流 stdout 相关联。

仅此而已。

我正在与gcc 4.9一起工作Ubuntu

感谢@NathanPierson。

他告诉我

std::basic_ostream 有一个构造函数,该构造函数接受指向 std::basic_streambuf 对象的指针。std::cout 使用指向 std::basic_streambuf 的某个实现定义的派生类的实例的指针进行初始化。

,这让我离答案更近了。

【问题讨论】:

  • 您可以创建一个 std::ostream 实例,但不能默认构造它。 See here 有关构造函数的更多信息。 std::cout 上的页面有更多关于它是如何初始化的细节——它是一个它连接到的实现定义的流缓冲区,并且实现还必须保证它在程序的早期适当地初始化。
  • @DrewMcGowen 但cout 实际上是std::ostream 的一个实例,而不是从std::ostream 派生的。
  • 对,那是正确的。 std::basic_ostream 有一个构造函数,它接受一个指向 std::basic_streambuf 对象的指针。 std::cout 使用指向 std::basic_streambuf 的某个实现定义的派生类的实例的指针进行初始化。如果您需要更多详细信息,则需要指定您要询问的实现方式。
  • 听起来您在看到标准库实现的实际代码之前不会感到满意。因此,您可能希望以浏览 GNU C++ 标准库的代码为起点。
  • @John 这取决于实现如何实现标准指定的行为。没有唯一的答案,只有每个实现决定做什么。

标签: c++ linux


【解决方案1】:

std::cout 是如何创建的?

首先,来自https://en.cppreference.com/w/cpp/io/ios_base/Init

std::ios_base::初始化

此类用于确保默认的 C++ 流 (std::cin, std::cout 等)被正确初始化和销毁​​。 [...]

标题 &lt;iostream&gt; 的行为就好像它定义了(直接或 间接地)具有静态存储的 std::ios_base::Init 实例 期间: [...]

嗯,让我们做一个真实的代码示例。我将使用GCC C++ library。来自https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/iostream#L73,这是重要的部分:

 // For construction of filebuffers for cout, cin, cerr, clog et. al.
 static ios_base::Init __ioinit;

现在我们跳转到ios_base::Init类的构造函数,在https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/src/c%2B%2B98/ios_init.cc#L85

ios_base::Init::Init()
  {
    if (__gnu_cxx::__exchange_and_add_dispatch(&_S_refcount, 1) == 0)
      {
    // Standard streams default to synced with "C" operations.
    _S_synced_with_stdio = true;

    new (&buf_cout_sync) stdio_sync_filebuf<char>(stdout);
    new (&buf_cin_sync) stdio_sync_filebuf<char>(stdin);
    new (&buf_cerr_sync) stdio_sync_filebuf<char>(stderr);

    // The standard streams are constructed once only and never
    // destroyed.
    new (&cout) ostream(&buf_cout_sync);
    new (&cin) istream(&buf_cin_sync);
    new (&cerr) ostream(&buf_cerr_sync);
    new (&clog) ostream(&buf_cerr_sync);
    cin.tie(&cout);
    cerr.setf(ios_base::unitbuf);
    // _GLIBCXX_RESOLVE_LIB_DEFECTS
    // 455. cerr::tie() and wcerr::tie() are overspecified.
    cerr.tie(&cout);

_S_refcount 用于当您从静态类的构造函数手动调用 ios_base::Init::Init(); 时,它可以防止双重初始化。

stdio_sync_filebufistream/ostream 的内部缓冲区,它用于处理cstdio FILE* 操作以获取/放置输入/输出数据,并在此处实现 https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/ext/stdio_sync_filebuf.h#L56 。它继承自std::basic_streambuf

所以cout是以stdio_sync_filebuf&lt;char&gt;为参数构造的in-place。这是这里提到的第一个构造函数https://en.cppreference.com/w/cpp/io/basic_ostream/basic_ostream

现在,因为这些东西是就地构建的,您可能想知道内存是如何分配的?来自https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/src/c%2B%2B98/globals_io.cc#L50

  // Standard stream objects.
  // NB: Iff <iostream> is included, these definitions become wonky.
  typedef char fake_istream[sizeof(istream)]
  __attribute__ ((aligned(__alignof__(istream))));
  typedef char fake_ostream[sizeof(ostream)]
  __attribute__ ((aligned(__alignof__(ostream))));
  fake_istream cin;
  fake_ostream cout;
  fake_ostream cerr;
  fake_ostream clog;

这些对象只是空的 char 适当大小和适当对齐的缓冲区。

是的,您可以自己构建 ostream,在 GCC 上使用 __gnu_cxx::stdio_sync_filebuf

#include <fstream>
#include <ext/stdio_sync_filebuf.h>
int main() {
    __gnu_cxx::stdio_sync_filebuf<char> mybuf_cout_sync(stdout);
    std::ostream os(&mybuf_cout_sync);
    os << "Hello world!
";
    return 0;
}

或者,为了便于移植,您可以编写自己的继承自 std::streambuf 的类,并自己从中构造 ostream。网上有很多例子,比如这里的https://stackoverflow.com/a/51250032/9072753

【讨论】:

  • 嗯。所以在globals_io.cc中,std::cin/std::cout/std::cerr只不过是char数组,它们对齐以匹配istream/ostream的要求。然而 iostream 标头和 ios_init.cc 将它们声明为实际的外部对象 istream/ostream 对象,并按原样使用它们(假设 [正确地] 在后一种情况下它们没有被初始化,因为它放置news 整个内存),我猜链接器对此没有问题,因为它只关心正确对齐的内存量是否在它期望的位置?诡异的。
  • @ShadowRanger 通常,链接器甚至不关心大小和对齐方式,尽管理论上它们可以......但是许多编译器不会为正确填充目标文件符号表中的 size 字段而烦恼,所以链接器必须应对这一点。
  • @ShadowRanger LTO 确实抱怨它,但前提是您使用 LTO 构建静态版本的 libstdc++,这与默认版本相去甚远。 gcc.gnu.org/bugzilla/show_bug.cgi?id=59472 gcc.gnu.org/bugzilla/show_bug.cgi?id=64275
  • MSVC 也使用类似的技巧来构造它的coutcincerr;我不是最新的细节,不过,我上次检查的是 2010 年或 2015 年的版本。
【解决方案2】:

编译器及其标准库实现可以使用非标准功能进行协作,这些功能仅由程序员无法使用。

在这种情况下这不是必需的,因为有一个非常标准的公共构造函数:

explicit basic_ostream(basic_streambuf<char_type, Traits>* sb);

如果你准备好了streambuf,你可以创建一个ostream类型的对象,标准库也可以。

streambuf 到底是什么是隐藏的实现细节,但在典型的实现中,它可能是从 stdout(C 风格的 &lt;cstdio&gt; 文件指针)构造的自定义类的对象。

【讨论】:

    【解决方案3】:

    我认为当前答案缺少的一部分,以及您的问题的一部分:
    std::cout这个名字也很“神奇”。
    这意味着标准库知道关于它,并提供特定于操作系统的必要连接到终端;使用相应的(和特定于操作系统的)系统调用进行输出等。

    【讨论】:

    • 谢谢你的解释。有什么参考吗?什么是背景管道?你能为我更详细地解释一下吗?
    猜你喜欢
    • 2020-03-12
    • 2013-11-01
    • 2020-04-26
    • 2010-11-30
    • 2011-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多