我在应用程序中使用C和C++代码的组合。
我想通过使用三元运算符来确定要打印的字符串,打印出布尔标志是true还是false,如下所示。
如果我使用const char*
,那么在程序启动之前,编译器不是很可能将这些字符串文字"Yes"
和"No"
存储在一些只读内存中吗。
如果我使用std::string
,当字符串超出范围时,它会被销毁吗?但我想编译器仍然需要将字符串文字"Yes"
和"No"
存储在某个地方?我不确定。
bool isSet = false;
// More code
//std::string isSetStr = isSet ? "Yes" : "No";
const char* isSetStr = isSet ? "Yes" : "No";
//printf ( "Flag is set ? : %sn", isSetStr.c_str());
printf ( "Flag is set ? : %sn", isSetStr);
任一版本都将在只读内存中分配字符串文本。任何一个版本都使用超出范围的局部变量,但字符串文字保留下来,因为它们不是本地存储的。
关于性能,C++容器类几乎总是比"容器"类效率更低;生的";C.当用g++-O3测试你的代码时,我得到了这个:
void test_cstr (bool isSet)
{
const char* isSetStr = isSet ? "Yes" : "No";
printf ( "Flag is set ? : %sn", isSetStr);
}
反汇编(x86(:
.LC0:
.string "Yes"
.LC1:
.string "No"
.LC2:
.string "Flag is set ? : %sn"
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 ? : %sn", isSetStr.c_str());
}
反汇编(x86(:
.LC0:
.string "Yes"
.LC1:
.string "No"
.LC2:
.string "Flag is set ? : %sn"
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
字符串文字仍然分配在只读内存中,因此部分是相同的。但我们得到了大量开销膨胀的代码。
但另一方面,到目前为止,这种情况下最大的瓶颈是控制台I/O,因此其余代码的性能甚至无关紧要。尽量编写可读性最好的代码,只在实际需要时进行优化。C中的手动字符串处理速度很快,但也很容易出错,而且很麻烦。
您可以使用godbolt进行测试。前者(使用const char*
(给出的是:
.LC0:
.string "No"
.LC1:
.string "Yes"
.LC2:
.string "Flag is set ? : %sn"
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 ? : %sn"
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 ? : %sn", isSetStr.data());
//printf ( "Flag is set ? : %sn", isSetStr);
}
给出:
.LC0:
.string "No"
.LC1:
.string "Yes"
.LC2:
.string "Flag is set ? : %sn"
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
都给出了最优编码。与CCD_ 13相比,CCD_。std::string
是用来处理字符串内容的,所以它在这里过于夸张,导致代码效率较低。
string_view
的另一个注释:它不能保证字符串以NUL结尾。在这种情况下,它是这样的,因为它是从一个以NUL结尾的静态字符串构建的。对于string_view
与printf
的通用用法,请使用printf("%.*s", str.length(), str.data());
编辑:通过禁用异常处理,您可以将std::string
版本减少为:
.LC0:
.string "Yes"
.LC1:
.string "No"
.LC2:
.string "Flag is set ? : %sn"
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
这仍然比CCD_ 20的版本多得多。注意,编译器足够聪明,可以在这里删除堆上的内存分配,但它仍然被迫计算字符串的长度(即使printf也会自己计算(。
冷静!
printf
将比程序源代码中嵌入的const char[]
数据的std::string
的任何构造慢数量级。
检查代码性能时始终使用探查器。编写一个小程序来测试一个假设,往往无法告诉你大程序中发生了什么。在您演示的情况下,一个好的编译器将优化到
int main(){printf ( "Flag is set ? : Non");}
字符串文字具有静态存储持续时间,它们在程序结束前一直有效。
请注意,如果您在程序中使用相同的字符串文字,则编译器没有必要将该字符串文字存储为一个对象。
这就是的表达
"Yes" == "Yes"
根据编译器选项的不同,可以产生true或false。但通常默认情况下,相同的字符串文字存储为一个字符串文字。
类型为std::string
的对象,如果它们没有在命名空间中声明并且没有关键字static
,则具有自动存储持续时间。这意味着,当控件传递到块时,每次控件离开块时,都会重新创建并销毁这样的对象。
isSet ? "Yes" : "No"
的类型是const char*
,与您将其存储在std::string
或const char*
(或std::stringview
,或…(中的事实无关。(因此编译器对字符串文字一视同仁(。
据quick-bench.com报道,
std::string
版本的速度慢了约6倍,这是可以理解的,因为它需要额外的动态分配。
除非您需要std::string
的额外功能,否则您可以使用const char*
。
等效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 ? : %sn", 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 ? : %sn", isSetStr.c_str());
}
https://godbolt.org/z/v3ebcsrYE