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

本文详细解析了std::endl在C++中的工作原理,包括其如何输出换行符并刷新缓冲区,以及与fflush函数的关系。通过源代码分析,解释了不同缓冲策略对程序性能的影响。

 

先抓出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::cout、std::cerr和std::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);

  }

}

 

  1. 全局函数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?

<think> 首先,用户的问题是:"我想了解C++std::cout与std:cout的含义和区别"。但用户写的是"std:cout",这看起来是个打字错误。应该是"std::cout"和另一个东西的对比。用户说"std::cout与std:cout",但第二个是"std:cout",可能是个错误。或许是"std::cout"和"std::cerr"或其他。用户提到了引用,但引用是关于std::cout, std::cerr, std::clog的。 回顾引用: - 引用[1]:讨论了C++中的三种预定义流对象:std::cout(标准输出)、std::cerr(标准错误)、std::clog(标准日志)。 - 引用[2]:关于std::cout的输出格式控制。 - 引用[3]:关于std::endl的使用示例。 用户的问题是"std::cout与std:cout的含义和区别"。第二个是"std:cout",这很可能是打字错误,应该是"std::cout"和另一个东西。或许是"std::cerr",因为引用中提到了它们。 在引用[1]中,明确提到了std::cout、std::cerr和std::clog。用户可能想说"std::cout与std::cerr",而不是"std:cout"。因为"std:cout"是无效的C++语法;应该是"std::cout"。 所以,我假设用户想问的是std::cout和std::cerr的含义和区别。 现在,系统指令要求: - 所有行内数学表达式必须使用$...$。 - 独立公式用$$...$$。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识[^1]等。 - 结构清晰,帮助用户逐步解决问题,回答真实可靠。 用户可见层:回答结构清晰,逐步解决问题。 所以,我需要: 1. 解释std::cout的含义。 2. 解释std::cerr的含义(假设用户指的是这个)。 3. 解释它们的区别。 4. 使用引用标识。 5. 生成相关问题。 步骤: - 先确认问题:用户说"std::cout与std:cout",但第二个可能是错误。我应该礼貌地纠正或假设是std::cerr。在回答中,可以说:"您提到了'std:cout',但可能是个打字错误。在C++中,常见的流对象包括std::cout和std::cerr等。我将解释std::cout和std::cerr的含义和区别。" 含义: - std::cout:标准输出流,用于输出正常信息到控制台。 - std::cerr:标准错误流,用于输出错误信息到控制台,通常不带缓冲。 区别: - 缓冲:cout通常是缓冲的,cerr是非缓冲的。 - 用途:cout用于一般输出,cerr用于错误消息。 - 重定向:可以独立重定向。 使用引用:引用[1]讨论了这些流对象。 生成相关问题:在回答结束时添加相关问题。 回答结构: 1. 澄清问题。 2. 解释std::cout的含义和用途。 3. 解释std::cerr的含义和用途。 4. 比较它们的区别。 5. 添加引用标识。 6. 生成相关问题。 使用中文。 </think>### C++std::cout与std::cerr的含义和区别 您的问题中提到“std::cout与std:cout”,但根据C++语法,“std:cout”可能是个打字错误(应为“std::cout”)。在C++标准库中,常见流对象包括std::cout、std::cerr和std::clog,我将重点解释std::cout和std::cerr的含义和区别,因为它们是最常用的输出流对象。以下内容基于C++标准库文档和用户提供的引用,我将逐步说明。 #### 1. **std::cout的含义和用途** - **含义**:std::cout是C++中的**标准输出流对象**,定义在`<iostream>`头文件中。它是`std::ostream`类的实例,用于向控制台输出正常程序信息(如结果、提示等)。 - **用途**:主要用于一般性输出,例如打印变量值、调试信息或用户交互内容。输出通常是缓冲的(缓冲机制可提高效率),这意味着输出内容可能不会立即显示在屏幕上,直到缓冲区满或手动刷新[^1]。 - **示例用法**: ```cpp #include <iostream> int main() { int num = 42; std::cout << "Hello, World! The number is: " << num << std::endl; // 输出到控制台 return 0; } ``` 在这个例子中,std::cout输出文本和变量值,std::endl用于刷新缓冲区并换行。 #### 2. **std::cerr的含义和用途** - **含义**:std::cerr是C++中的**标准错误流对象**,同样定义在`<iostream>`头文件中。它也是`std::ostream`类的实例,但专门用于输出错误或警告信息。 - **用途**:设计为**非缓冲流**,输出内容会立即显示在控制台上,确保错误信息不被延迟。这适用于调试、崩溃报告或紧急通知,避免因缓冲区未刷新而丢失关键信息[^1]。 - **示例用法**: ```cpp #include <iostream> int main() { int value; std::cin >> value; if (value < 0) { std::cerr << "错误: 输入值不能为负数!" << std::endl; // 立即输出错误 } return 0; } ``` 这里,std::cerr在输入无效时直接输出错误消息。 #### 3. **std::cout与std::cerr的区别** 以下是两者的核心区别,基于它们的特性和最佳实践: | 特性 | std::cout (标准输出) | std::cerr (标准错误) | |---------------------|------------------------------------------|------------------------------------------| | **缓冲机制** | 通常是缓冲的(输出可能延迟) | 非缓冲的(输出立即显示)[^1] | | **主要用途** | 输出正常程序信息(如计算结果、日志) | 输出错误或警告信息(如输入验证失败) | | **重定向能力** | 可通过命令行重定向(如`./program > output.txt`) | 通常不被重定向,确保错误直接显示在终端 | | **性能影响** | 缓冲机制提高效率,适合大量输出 | 非缓冲可能降低性能,但保证及时性 | | **格式控制** | 支持精细控制(如精度设置,参考引用[^2]) | 同样支持格式控制,但较少用于复杂格式化 | | **与std::endl交互** | 使用std::endl会强制刷新缓冲区[^3] | std::endl同样有效,但非缓冲特性使其更可靠 | **关键区别总结**: - std::cout适合常规输出,而std::cerr专用于错误处理。 - 在程序设计中,推荐使用std::cerr输出错误,以避免正常输出缓冲导致的问题(例如,在崩溃前确保错误消息可见)。 - 引用[1]强调,std::cerr和std::cout是独立的流,可同时使用而不会相互干扰[^1]。 #### 4. **实际应用建议** - **何时使用哪个**: - 用std::cout输出用户可见的结果(如计算结果)。 - 用std::cerr输出调试错误或异常(如文件打开失败)。 - **结合使用示例**: ```cpp #include <iostream> #include <fstream> int main() { std::ifstream file("data.txt"); if (!file.is_open()) { std::cerr << "错误: 文件无法打开!" << std::endl; // 非缓冲错误输出 return 1; } int data; file >> data; std::cout << "读取的数据: " << data << std::endl; // 缓冲的正常输出 return 0; } ``` 在这个例子中,文件错误通过std::cerr立即报告,而成功读取的数据通过std::cout输出。 通过以上解释,您应该清楚了std::cout和std::cerr的核心差异。如需更深入的细节,请参考C++标准库文档或相关资源[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值