`std::cout` 是如何实现的?

Joh*_*ohn 46 c++ linux

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

extern ostream cout;      /// Linked to standard output
Run Code Online (Sandbox Code Playgroud)

并且std::ostream由 定义typedef std::basic_ostream<char> std::ostream

更重要的是,您似乎无法创建 的实例std::ostream。请参阅此演示代码片段

#include<iostream>

int main()
{
    std::ostream os;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

以下是编译器对上面代码片段的抱怨:

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;
                  ^
Run Code Online (Sandbox Code Playgroud)

问题来了,既然被std::basic_ostream<_CharT, _Traits>::basic_ostream()标记为protected,那么std::cout是如何创建的呢?

CppReference 上的这个链接似乎没有多大意义。它没有清楚地告诉我 的构造函数std::cout是如何实现以及如何创建的。据我所知,最相关的信息是:std::coutstd::ostream

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

仅此而已。

我正在Ubuntugcc 4.9

感谢@NathanPierson。

他告诉我

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

,这让我更接近答案。

Kam*_*Cuk 43

std::cout 是如何创建的?

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

std::ios_base::Init

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

标头的<iostream>行为就好像它定义(直接或间接)具有静态存储持续时间的 std::ios_base::Init 实例:[...]

嗯,让我们来做一个真正的代码示例。我将使用GCC C++ 库。来自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;
Run Code Online (Sandbox Code Playgroud)

现在我们跳转到类的构造函数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);
Run Code Online (Sandbox Code Playgroud)

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

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

Socout是用as 参数就地stdio_sync_filebuf<char>构建的。这是这里提到的第一个构造函数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;
Run Code Online (Sandbox Code Playgroud)

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

是的,你可以自己构建 ostream,在__gnu_cxx::stdio_sync_filebufGCC 上:

#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!\n";
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

或者,为了便于移植,您可以编写自己的类,自己继承std::streambuf并构造ostream该类。网上有很多例子,例如这里/sf/answers/3587502271/

  • @ShadowRanger 通常,链接器甚至不关心大小和对齐方式,尽管理论上它们可以......但是许多编译器不关心正确填充目标文件符号表中的“大小”字段,因此链接器必须处理这个问题。 (3认同)
  • @ShadowRanger LTO 确实对此有所抱怨,但前提是您使用 LTO 构建 libstdc++ 的静态版本,这与默认版本相去甚远。https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59472 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64275 (2认同)

n. *_* m. 17

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

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

explicit basic_ostream(basic_streambuf<char_type, Traits>* sb);
Run Code Online (Sandbox Code Playgroud)

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

这到底是什么是隐藏的实现细节,但在典型的实现中,它可能是由(C 样式文件指针)streambuf构造的自定义类的对象。stdout<cstdio>