[英]Concatenation precedence in case of std::string and const char
[英]What is more efficient in this case, using const char* or std::string
我在我的应用程序中使用了 C 和 C++ 代码的组合。
我想打印出 boolean 标志是真还是假,如下所示,通过使用三元运算符来确定要打印的字符串。
如果我使用const char*
,编译器不会在程序启动之前将这些字符串文字"Yes"
和"No"
存储在一些只读 memory 中。
如果我使用std::string
,当字符串超出 scope 时,它会被销毁吗? 但我猜编译器仍然需要在某处存储字符串文字"Yes"
和"No"
? 我不确定。
bool isSet = false;
// More code
//std::string isSetStr = isSet ? "Yes" : "No";
const char* isSetStr = isSet ? "Yes" : "No";
//printf ( "Flag is set ? : %s\n", isSetStr.c_str());
printf ( "Flag is set ? : %s\n", isSetStr);
任一版本都会在只读 memory 中分配字符串文字本身。 任何一个版本都使用 scope 之外的局部变量,但字符串文字仍然存在,因为它们没有存储在本地。
关于性能,C++ 容器类几乎总是比“原始”C 效率低下。 使用 g++ -O3 测试您的代码时,我得到以下信息:
void test_cstr (bool isSet)
{
const char* isSetStr = isSet ? "Yes" : "No";
printf ( "Flag is set ? : %s\n", isSetStr);
}
反汇编(x86):
.LC0:
.string "Yes"
.LC1:
.string "No"
.LC2:
.string "Flag is set ? : %s\n"
test_cstr(bool):
test dil, dil
mov eax, OFFSET FLAT:.LC1
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:.LC2
cmove rsi, rax
xor eax, eax
jmp printf
字符串文字被加载到只读位置,并且isSetStr
变量被简单地优化掉。
现在尝试使用相同的编译器和选项(-O3):
void test_cppstr (bool isSet)
{
std::string isSetStr = isSet ? "Yes" : "No";
printf ( "Flag is set ? : %s\n", isSetStr.c_str());
}
反汇编(x86):
.LC0:
.string "Yes"
.LC1:
.string "No"
.LC2:
.string "Flag is set ? : %s\n"
test_cppstr(bool):
push r12
mov eax, OFFSET FLAT:.LC1
push rbp
push rbx
mov ebx, OFFSET FLAT:.LC0
sub rsp, 32
test dil, dil
cmove rbx, rax
lea rbp, [rsp+16]
mov QWORD PTR [rsp], rbp
mov rdi, rbx
call strlen
xor edx, edx
mov esi, eax
test eax, eax
je .L7
.L6:
mov ecx, edx
add edx, 1
movzx edi, BYTE PTR [rbx+rcx]
mov BYTE PTR [rbp+0+rcx], dil
cmp edx, esi
jb .L6
.L7:
mov QWORD PTR [rsp+8], rax
mov edi, OFFSET FLAT:.LC2
mov BYTE PTR [rsp+16+rax], 0
mov rsi, QWORD PTR [rsp]
xor eax, eax
call printf
mov rdi, QWORD PTR [rsp]
cmp rdi, rbp
je .L1
call operator delete(void*)
.L1:
add rsp, 32
pop rbx
pop rbp
pop r12
ret
mov r12, rax
jmp .L4
test_cppstr(bool) [clone .cold]:
.L4:
mov rdi, QWORD PTR [rsp]
cmp rdi, rbp
je .L5
call operator delete(void*)
.L5:
mov rdi, r12
call _Unwind_Resume
字符串文字仍然分配在只读 memory 中,因此该部分是相同的。 但是我们得到了大量的开销膨胀代码。
但另一方面,在这种情况下,迄今为止最大的瓶颈是控制台 I/O,因此代码的 rest 的性能甚至不相关。 努力编写尽可能易读的代码,并仅在您真正需要时进行优化。 C 中的手动字符串处理速度很快,但也非常容易出错和繁琐。
您可以使用Godbolt对其进行测试。 前者(使用const char*
)给出了这个:
.LC0:
.string "No"
.LC1:
.string "Yes"
.LC2:
.string "Flag is set ? : %s\n"
a(bool):
test dil, dil
mov eax, OFFSET FLAT:.LC0
mov esi, OFFSET FLAT:.LC1
cmove rsi, rax
mov edi, OFFSET FLAT:.LC2
xor eax, eax
jmp printf
后者(使用 std::string)给出了这个:
.LC0:
.string "Yes"
.LC1:
.string "No"
.LC2:
.string "Flag is set ? : %s\n"
a(bool):
push r12
push rbp
mov r12d, OFFSET FLAT:.LC1
push rbx
mov esi, OFFSET FLAT:.LC0
sub rsp, 32
test dil, dil
lea rax, [rsp+16]
cmovne r12, rsi
or rcx, -1
mov rdi, r12
mov QWORD PTR [rsp], rax
xor eax, eax
repnz scasb
not rcx
lea rbx, [rcx-1]
mov rbp, rcx
cmp rbx, 15
jbe .L3
mov rdi, rcx
call operator new(unsigned long)
mov QWORD PTR [rsp+16], rbx
mov QWORD PTR [rsp], rax
.L3:
cmp rbx, 1
mov rax, QWORD PTR [rsp]
jne .L4
mov dl, BYTE PTR [r12]
mov BYTE PTR [rax], dl
jmp .L5
.L4:
test rbx, rbx
je .L5
mov rdi, rax
mov rsi, r12
mov rcx, rbx
rep movsb
.L5:
mov rax, QWORD PTR [rsp]
mov QWORD PTR [rsp+8], rbx
mov edi, OFFSET FLAT:.LC2
mov BYTE PTR [rax-1+rbp], 0
mov rsi, QWORD PTR [rsp]
xor eax, eax
call printf
mov rdi, QWORD PTR [rsp]
lea rax, [rsp+16]
cmp rdi, rax
je .L6
call operator delete(void*)
jmp .L6
mov rdi, QWORD PTR [rsp]
lea rdx, [rsp+16]
mov rbx, rax
cmp rdi, rdx
je .L8
call operator delete(void*)
.L8:
mov rdi, rbx
call _Unwind_Resume
.L6:
add rsp, 32
xor eax, eax
pop rbx
pop rbp
pop r12
ret
使用std::string_view
例如:
#include <stdio.h>
#include <string_view>
int a(bool isSet) {
// More code
std::string_view isSetStr = isSet ? "Yes" : "No";
//const char* isSetStr = isSet ? "Yes" : "No";
printf ( "Flag is set ? : %s\n", isSetStr.data());
//printf ( "Flag is set ? : %s\n", isSetStr);
}
给出:
.LC0:
.string "No"
.LC1:
.string "Yes"
.LC2:
.string "Flag is set ? : %s\n"
a(bool):
test dil, dil
mov eax, OFFSET FLAT:.LC0
mov esi, OFFSET FLAT:.LC1
cmove rsi, rax
mov edi, OFFSET FLAT:.LC2
xor eax, eax
jmp printf
综上所述, const char*
和string_view
给出了最佳代码。 与const char*
相比, string_view
需要输入更多代码。 std::string
是用来操作字符串内容的,所以在这里它是矫枉过正的,并且会导致代码效率降低。
string_view
的另一个注释:它不保证字符串是 NUL 终止的。 在这种情况下,它是由 NUL 终止的 static 字符串构建的。 对于printf
的通用string_view
用法,请使用printf("%.*s", str.length(), str.data());
编辑:通过禁用异常处理,您可以将std::string
版本减少为:
.LC0:
.string "Yes"
.LC1:
.string "No"
.LC2:
.string "Flag is set ? : %s\n"
a(bool):
push r12
mov eax, OFFSET FLAT:.LC1
push rbp
mov ebp, OFFSET FLAT:.LC0
push rbx
sub rsp, 32
test dil, dil
cmove rbp, rax
lea r12, [rsp+16]
mov QWORD PTR [rsp], r12
mov rdi, rbp
call strlen
mov rsi, rbp
mov rdi, r12
lea rdx, [rbp+0+rax]
mov rbx, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_S_copy_chars(char*, char const*, char const*)
mov rax, QWORD PTR [rsp]
mov QWORD PTR [rsp+8], rbx
mov edi, OFFSET FLAT:.LC2
mov BYTE PTR [rax+rbx], 0
mov rsi, QWORD PTR [rsp]
xor eax, eax
call printf
mov rdi, rsp
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_dispose()
add rsp, 32
pop rbx
pop rbp
pop r12
ret
这仍然比string_view
的版本要多得多。 请注意,编译器足够聪明,可以在此处删除堆上的 memory 分配,但它仍然被迫计算字符串的长度(即使 printf 也会自行计算)。
冷静下来!
printf
将比从程序源代码中嵌入的const char[]
数据构造std::string
慢几个数量级。
检查代码性能时始终使用分析器。 编写一个小程序来尝试检验一个假设通常无法告诉您有关您的大程序中正在发生的事情的任何信息。 在您介绍的情况下,一个好的编译器将优化为
int main(){printf ( "Flag is set ? : No\n");}
字符串文字具有 static 存储持续时间,它们在程序结束之前一直有效。
请注意,如果您在程序中使用相同的字符串文字,编译器不必将此字符串文字存储为一个 object。
就是这个表情
"Yes" == "Yes"
根据编译器选项,可以产生真或假。 但通常默认情况下,相同的字符串文字存储为一个字符串文字。
std::string
类型的对象如果它们没有在命名空间中声明并且没有关键字static
具有自动存储持续时间。 这意味着当控件被传递给一个块时,这样的 object 会在每次控件离开该块时重新创建并销毁。
等效的 C++ 代码:
#include <string>
using namespace std::string_literals;
void test_cppstr (bool isSet)
{
const std::string& isSetStr = isSet ? "Yes"s : "No"s;
printf ( "Flag is set ? : %s\n", isSetStr.c_str());
}
效率几乎像 C 版本。
编辑:此版本的设置/退出开销很小,但在调用printf
时与 C 代码具有相同的效率。
#include <string>
using namespace std::string_literals;
const std::string yes("Yes");
const std::string no("No");
void test_cppstr (bool isSet)
{
const std::string& isSetStr = isSet ? yes : no;
printf ( "Flag is set ? : %s\n", isSetStr.c_str());
}
isSet? "Yes": "No"
isSet? "Yes": "No"
是const char*
,与您将其存储在std::string
或const char*
(或std::stringview
或...)中的事实无关。 (因此编译器会平等对待字符串文字)。
根据quick-bench.com ,
std::string
版本慢了约 6 倍,这是可以理解的,因为它需要额外的动态分配。
除非您需要std::string
的额外功能,否则您可能会继续使用const char*
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.