蓝天

C++标准库中的std::endl究竟做了什么?

 

先抓出std::endl的源代码:

/**

 *  @file  ostream

 *  @brief  Write a newline and flush the stream.

 *

 *  This manipulator is often mistakenly used when a simple newline is

 *  desired, leading to poor buffering performance.  See

 *  http://gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt11ch25s02.html

 *  for more on this subject.

*/

template<typename _CharT, typename _Traits>

inline basic_ostream<_CharT, _Traits>&

endl(basic_ostream<_CharT, _Traits>& __os)

{

  return flush(__os.put(__os.widen('\n')));

}

 

可以看到endl实际是名字空间std中的一个全局内联函数记住std::endl是一个函数),它做了两件事:

1) 输出一个换行符(为何要输出一个换行符,如果不输出会怎么样?);

2) 调用flush

 

继续看flush这个函数(也是std名字空间内的全局内联函数):

/**

 *  @brief  Flushes the output stream.

 *

 *  This manipulator simply calls the stream's @c flush() member function.

*/

template<typename _CharT, typename _Traits>

inline basic_ostream<_CharT, _Traits>&

flush(basic_ostream<_CharT, _Traits>& __os)

{

  return __os.flush(); // 注意这里的os不是操作系统的意思,而是类basic_ostream的缩写

}

 

进一步查看std::basic_ostream::flush的代码:

/**

 *  @file  ostream.tcc

*/

template<typename _CharT, typename _Traits>

basic_ostream<_CharT, _Traits>&

basic_ostream<_CharT, _Traits>::flush()

{

  // _GLIBCXX_RESOLVE_LIB_DEFECTS

  // DR 60. What is a formatted input function?

  // basic_ostream::flush() is *not* an unformatted output function.

  ios_base::iostate __err = ios_base::goodbit;

 

  __try

  {

    // 刷新发生在pubsync,底层调用的是LIBC库函数fflush

    if (this->rdbuf() && this->rdbuf()->pubsync() == -1)

      __err |= ios_base::badbit;

  }

  __catch(__cxxabiv1::__forced_unwind&)

  {

    this->_M_setstate(ios_base::badbit);

    __throw_exception_again;

  }

  __catch(...)

  {

    this->_M_setstate(ios_base::badbit);

  }

  if (__err)

    this->setstate(__err);

  return *this;

}

 

下段小代码,可以看到两个帮助释疑的调用栈,:

$ cat xxx.cpp

#include <iostream>

int main() {

  std::cout << std::endl;

  return 0;

}

 

写换符符:

#0  0x00007ffff72e0840 in write () from /lib64/libc.so.6

#1  0x00007ffff726cfb3 in _IO_new_file_write () from /lib64/libc.so.6

#2  0x00007ffff726e41c in __GI__IO_do_write () from /lib64/libc.so.6

#3  0x00007ffff726e7f3 in __GI__IO_file_overflow () from /lib64/libc.so.6

#4  0x00007ffff726ac49 in putc () from /lib64/libc.so.6

#5  0x00007ffff7b694c6 in std::ostream::put(char) () from /lib64/libstdc++.so.6

#6  0x00007ffff7b69712 in std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&) ()

   from /lib64/libstdc++.so.6

#7  0x0000000000400803 in main () at xxx.cpp:3

 

刷新流:

#0  0x00007ffff7262030 in fflush () from /lib64/libc.so.6

#1  0x00007ffff7b68d5e in std::ostream::flush() () from /lib64/libstdc++.so.6

#2  0x0000000000400803 in main () at xxx.cpp:3

 

实际上,还可以看到更多,如全局对象std::coutstd::cerrstd::clog等的析构:

#0  0x00007ffff7262030 in fflush () from /lib64/libc.so.6

#1  0x00007ffff7b68d5e in std::ostream::flush() () from /lib64/libstdc++.so.6

#2  0x00007ffff7b41bc8 in std::ios_base::Init::~Init() () from /lib64/libstdc++.so.6

#3  0x00007ffff722fa69 in __run_exit_handlers () from /lib64/libc.so.6

#4  0x00007ffff722fab5 in exit () from /lib64/libc.so.6

#5  0x00007ffff7218c0c in __libc_start_main () from /lib64/libc.so.6

#6  0x0000000000400729 in _start ()

 

Init析构的同时析构cout等:

/**

 *  @file  ios_init.cc

*/

ios_base::Init::~Init()

{

  // Be race-detector-friendly.  For more info see bits/c++config.

  _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_S_refcount);

  if (__gnu_cxx::__exchange_and_add_dispatch(&_S_refcount, -1) == 2)

  {

    _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_S_refcount);

    // Catch any exceptions thrown by basic_ostream::flush()

    __try

    {

      // Flush standard output streams as required by 27.4.2.1.6

      cout.flush();

      cerr.flush();

      clog.flush();

    

  #ifdef _GLIBCXX_USE_WCHAR_T

      wcout.flush();

      wcerr.flush();

      wclog.flush();

  #endif

    }

    __catch(...)

    {

    }

  }

}

 

文件ios_init.cc其它相关函数源码:

1) std::ios_base::Init的构造函数

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);

 

#ifdef _GLIBCXX_USE_WCHAR_T

    new (&buf_wcout_sync) stdio_sync_filebuf<wchar_t>(stdout);

    new (&buf_wcin_sync) stdio_sync_filebuf<wchar_t>(stdin);

    new (&buf_wcerr_sync) stdio_sync_filebuf<wchar_t>(stderr);

 

    new (&wcout) wostream(&buf_wcout_sync);

    new (&wcin) wistream(&buf_wcin_sync);

    new (&wcerr) wostream(&buf_wcerr_sync);

    new (&wclog) wostream(&buf_wcerr_sync);

    wcin.tie(&wcout);

    wcerr.setf(ios_base::unitbuf);

    wcerr.tie(&wcout);

#endif

 

    // NB: Have to set refcount above one, so that standard

    // streams are not re-initialized with uses of ios_base::Init

    // besides <iostream> static object, ie just using <ios> with

    // ios_base::Init objects.

    __gnu_cxx::__atomic_add_dispatch(&_S_refcount, 1);

  }

}

 

2) 全局函数ios_base::sync_with_stdio

bool

ios_base::sync_with_stdio(bool __sync)

{

  // _GLIBCXX_RESOLVE_LIB_DEFECTS

  // 49.  Underspecification of ios_base::sync_with_stdio

  bool __ret = ios_base::Init::_S_synced_with_stdio;

 

  // Turn off sync with C FILE* for cin, cout, cerr, clog iff

  // currently synchronized.

  if (!__sync && __ret)

  {

    // Make sure the standard streams are constructed.

    ios_base::Init __init;

 

    ios_base::Init::_S_synced_with_stdio = __sync;

 

    // Explicitly call dtors to free any memory that is

    // dynamically allocated by filebuf ctor or member functions,

    // but don't deallocate all memory by calling operator delete.

    buf_cout_sync.~stdio_sync_filebuf<char>();

    buf_cin_sync.~stdio_sync_filebuf<char>();

    buf_cerr_sync.~stdio_sync_filebuf<char>();

 

#ifdef _GLIBCXX_USE_WCHAR_T

    buf_wcout_sync.~stdio_sync_filebuf<wchar_t>();

    buf_wcin_sync.~stdio_sync_filebuf<wchar_t>();

    buf_wcerr_sync.~stdio_sync_filebuf<wchar_t>();

#endif

 

    // Create stream buffers for the standard streams and use

    // those buffers without destroying and recreating the

    // streams.

    new (&buf_cout) stdio_filebuf<char>(stdout, ios_base::out);

    new (&buf_cin) stdio_filebuf<char>(stdin, ios_base::in);

    new (&buf_cerr) stdio_filebuf<char>(stderr, ios_base::out);

    cout.rdbuf(&buf_cout);

    cin.rdbuf(&buf_cin);

    cerr.rdbuf(&buf_cerr);

    clog.rdbuf(&buf_cerr);

 

#ifdef _GLIBCXX_USE_WCHAR_T

  new (&buf_wcout) stdio_filebuf<wchar_t>(stdout, ios_base::out);

  new (&buf_wcin) stdio_filebuf<wchar_t>(stdin, ios_base::in);

  new (&buf_wcerr) stdio_filebuf<wchar_t>(stderr, ios_base::out);

  wcout.rdbuf(&buf_wcout);

  wcin.rdbuf(&buf_wcin);

  wcerr.rdbuf(&buf_wcerr);

  wclog.rdbuf(&buf_wcerr);

#endif

  }

  return __ret;

}

 

前面提到的endl为何要输出一个换行符,这是因为fflush只是将数据刷到标准输出,标准输出本身也是有缓存的,而且默认是行缓存_IOLBF line buffered)。

是否可将标准输出设置为全缓存_IOFBF fully buffered)了?答案是可以,执行下列代码即可得到结果。

$ cat eee.cpp

#include <stdio.h>

int main(void) {

  char buf[BUFSIZ];

  setvbuf(stdout, buf, _IOFBF, BUFSIZ);

  printf("Hello, world!\n");

  getchar();

  return 0;

}

 

是否行缓存和全缓存,与是否为字符设备(Character Device,只能顺序读取)或块设备(Block Device,支持随机存取)无关。

将标准输出定向到普通文件,则缓存类型同普通文件,即默认全缓存:

$ cat eee.cpp

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

int main(int argc, char* argv[]) {

  char buf[BUFSIZ];

  int fd = open("/tmp/xxx.txt", O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU);

  if (fd == -1) {

    perror("open");

    return 1;

  } else {

    int fdnew = dup2(fd, STDOUT_FILENO); // 让标准输出指向文件

    if (fdnew == -1) {

      perror("dup2");

      return 1;

    } else {

      printf("123\n");

      getchar();

      return 0;

    }

  }

}

 

匿名管道默认是全缓存,可用下列代码验证:

$ cat xxx.cpp

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <sys/wait.h>

#include <unistd.h>

int main(int argc, char* argv[]) {

  int fd[2];

  char buf[BUFSIZ];

  if (pipe(fd) == -1) {

    perror("pipe");

    exit(1);

  }

 

  pid_t pid = fork();

  if (pid == -1) {

    perror("fork");

    exit(1);

  } else if (pid == 0) { // Child process

    dup2(fd[1], STDOUT_FILENO);

    //setvbuf(stdout, buf, _IOLBF, BUFSIZ);

    printf("hello\n");

    getchar();

    fflush(stdout);

    _exit(0);

  } else { // Parent process

    char msg[BUFSIZ];

    int n = read(fd[0], msg, sizeof(msg)-1);

    if (n == -1) {

      perror("Parent read");

    } else {

      msg[n] = '\0';

      fprintf(stdout, "(%d)%s\n", n, msg);

    }

    

    wait(NULL);

    exit(0);

  }

}

 

有名管道和套接字(包含UNIX套接字也是全缓存),附问题:如何在GDB中调试跟踪std::cout

posted on 2019-11-13 16:15  #蓝天  阅读(2785)  评论(0编辑  收藏  举报

导航