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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM