7

通常在使用 C++ 编码时,我会cout用换行符 ( \n) 结束语句。但是,我的直觉一直是将这个换行符表示为字符串文字: "\n",即使它是单个字符并且可以更有效地表示为字符文字 ( '\n')。例如:

cout << "The value of var is " << var << "\n";

很多代码都有这种现象。所以,问题如下:

  1. 表示换行符常量的两种不同方式的效率有什么不同吗?我不关心在生成的程序的执行中产生任何实际差异(我想这将是微不足道的);而是让我感到困扰的是,一些效率,无论多么微不足道,都可能无缘无故地丢失。

  2. 如果字符串文字版本效率较低,编译器是否会将其优化为字符常量版本,因为两者提供完全相同的行为?

  3. 我也熟悉std::endl。文档说“当需要一个简单的换行符时,这个操纵器经常被错误地使用,导致缓冲性能很差。” 并指向本文以获取更多信息。但是,那篇文章指出,提到的“性能差”仅适用于文件 I/O,并且endl用于写入屏幕实际上可能会提高性能。这有什么关系?

我搜索了 C++ 标准库,但找不到<<运算符相关重载的实现。我在以下位置找到了声明ostream.tcc

extern template ostream& operator<<(ostream&, char);
extern template ostream& operator<<(ostream&, const char*);

但是没有关于机制如何在实现中归结的线索。

这更像是一个理论问题,所以我对阅读“两者之间没有实际区别”不感兴趣。我知道。我只是想知道是否有任何区别以及编译器如何处理它。

4

3 回答 3

3

They are probably optimized to one string (per compilation unit) - most compilers will "merge strings of the same content".

I'd expect there to be very little practical difference, other than the fact that you pass a pointer to a single char string.

To your concrete questions:

  1. Yes, there is a slight difference, as a char * will require some indirection and thus generate a few extra instructions to be executed. For console output (rather than output to file) it's not important, as scrolling the console, even in full-screen text mode is > 100x more instructions.
  2. Doubt it.
  3. So std::endl will flush the buffer, which does indeed reduce output to files, because partial sectors or blocks are being written to the file, which increases the system call overhead. If you use "\n", the file is not being flushed until the buffer itself is filled, which would be at the very least 512 bytes, possibly as much as several tens of kilobytes. But as for answer #1, the console output performance will be more dependent on the speed that the screen can scroll.
于 2013-03-31T00:54:22.040 回答
2

The different between string literal \n and endl is that:

\n is a string literal that get appended to stdout. endl will also append the newline character to stdout, however, it will also flush the stdout buffer. Therefore, it may take more processing. Other than this, there should be no practical difference.

于 2013-03-31T00:52:33.857 回答
1

我强烈怀疑它,因为它改变了内存布局(一个有一个空终止符,另一个没有),而且因为它会涉及改变文字的实际类型(并且,通过扩展,改变被调用的函数)。因此,在绝大多数情况下,这将是无效的转变,对极少数情况下的帮助还不够。

That said, if the compiler does enough aggressive inlining (inlining the function itself and the constant data into the function), you might end up with effectively the same code. For example, Clang compiles the following:

#include <iostream>
using namespace std;

int main() {
    cout << "X" << "\n";
    cout << "Y" << '\n';
}             

into this:

movq    std::cout@GOTPCREL(%rip), %rbx
leaq    L_.str(%rip), %rsi
movq    %rbx, %rdi
movl    $1, %edx
callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
leaq    L_.str1(%rip), %rsi
movq    %rbx, %rdi
movl    $1, %edx
callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
leaq    L_.str2(%rip), %rsi
movq    %rbx, %rdi
movl    $1, %edx
callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
leaq    -9(%rbp), %rsi
movb    $10, -9(%rbp)
movq    %rbx, %rdi
movl    $1, %edx
callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
xorl    %eax, %eax
addq    $8, %rsp
popq    %rbx
popq    %rbp

As you can see, inlining has made the two cases nearly identical. (And, in fact, the '\n' case is slightly more complex because the character has to be put on the stack.)

于 2013-03-31T00:49:11.737 回答