In this answer we can read that:
I suppose there's little difference between using
'\n'
or using"\n"
, but the latter is an array of (two) characters, which has to be printed character by character, for which a loop has to be set up, which is more complex than outputting a single character.
emphasis mine
That makes sense to me. I would think that outputting a const char*
requires a loop which will test for null-terminator, which must introduce more operations than, let's say, a simple putchar
(not implying that std::cout
with char
delegates to calling that - it's just a simplification to introduce an example).
That convinced me to use
std::cout << '\n';
std::cout << ' ';
rather than
std::cout << "\n";
std::cout << " ";
It's worth to mention here that I am aware of the performance difference being pretty much negligible. Nonetheless, some may argue that the former approach carries intent of actually passing a single character, rather than a string literal that just happened to be a one char
long (two char
s long if you count the '\0'
).
Lately I've done some little code reviews for someone who was using the latter approach. I made a small comment on the case and moved on. The developer then thanked me and said that he hadn't even thought of such difference (mainly focusing on the intent). It was not impactful at all (unsurprisingly), but the change was adopted.
I then began wondering how exactly is that change significant, so I ran to godbolt. To my surprise, it showed the following results when tested on GCC (trunk) with -std=c++17 -O3
flags. The generated assembly for the following code:
#include <iostream>
void str() {
std::cout << "\n";
}
void chr() {
std::cout << '\n';
}
int main() {
str();
chr();
}
surprised me, because it appears that chr()
is actually generating exactly twice as many instructions as str()
does:
.LC0:
.string "\n"
str():
mov edx, 1
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
jmp 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)
chr():
sub rsp, 24
mov edx, 1
mov edi, OFFSET FLAT:_ZSt4cout
lea rsi, [rsp+15]
mov BYTE PTR [rsp+15], 10
call 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)
add rsp, 24
ret
Why is that? Why both of them eventually call the same std::basic_ostream
function with const char*
argument? Does it mean that the char
literal approach is not only not better, but actually worse than string literal one?