一字符字符串字面值是否会被优化为简单的char类型字面值?

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

但是没有线索表明实现中的机制如何简化。

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


1
存在的唯一“一个字符字符串字面量”是空字符串。永远不要忘记零终止符。 - Hans Passant
"\n" 是一个类型为 const char[2] 的字符,如何将其 优化 为单个字符?此外,您的第三个问题似乎与此无关,可能应该作为单独的问题发布。 - Praetorian
3
楼主已经完全了解存储差异,伙计们。这在第一段中有涉及。楼主询问的是编译器是否可以用这个特殊的单字符情况代替C字符串。这个单个字符不需要终止符。 - justin
3
@Praetorian:当然,'\n'"\n"是不同的,通常情况下后者不能被优化为前者。但是,cout << '\n'cout << "\n"具有完全相同的行为,编译器可以利用其对cout和重载的<<运算符的了解来将一个转换为另一个。是否有任何编译器真正这样做是另一个问题。 - Keith Thompson
3个回答

3

它们可能被优化为一个字符串(每个编译单元一种)- 大多数编译器将“合并相同内容的字符串”。

实际上,除了您传递指向单个字符字符串的指针外,我不认为有很大的实际区别。

回答您具体的问题:

  1. 是的,有轻微的区别,因为char *将需要一些间接操作,从而产生一些额外的指令要执行。对于控制台输出(而不是输出到文件),这不重要,因为即使在全屏文本模式下滚动控制台也比100倍更多的指令。
  2. 我怀疑没有。
  3. 所以std::endl将刷新缓冲区,这确实会减少将输出写入文件的次数,因为部分扇区或块正在被写入文件,这增加了系统调用开销。如果使用“\n”,则在缓冲区本身被填充之前,文件不会被刷新,这至少会是512字节,可能是数十千字节。但是就答案#1而言,控制台输出性能更依赖于屏幕滚动的速度。

2

\nendl 的区别在于:

\n 是一个字符串字面值,附加到标准输出(stdout)之后。 endl 也会将换行字符附加到标准输出(stdout),但是它还会刷新stdout的缓冲区。因此,可能需要更多的处理时间。除此之外,没有实际区别。


1
我非常怀疑这一点,因为它会改变内存布局(一个有空终止符,另一个没有),并且它将涉及更改文字本身的实际类型(以及由此延伸的更改调用的函数)。因此,在绝大多数情况下,这将是无效的转换,并且在微小的少数情况下也不能算是足够有帮助的。
话虽如此,如果编译器进行了足够积极的内联(将函数本身和常量数据内联到函数中),您最终可能会得到相同的代码。例如,Clang编译以下内容:
#include <iostream>
using namespace std;

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

变成这样:

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

正如您所看到的,内联使得这两种情况几乎相同。实际上,'\n'的情况略微更加复杂,因为字符必须放在堆栈上。

2
另一方面,我记得看到printf("\n")被优化为putc('\n'),所以我对这样的优化并不感到惊讶。 - Matteo Italia
"printf" 是 C 语言内置函数,可以通过各种方式进行优化。例如,printf("blah\n") 会转换为 puts("blah") - nneonneo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接