C++ 在这种情况下,使用const char*或std::string更有效率的是什么
我在应用程序中使用C和C++代码的组合。 我想通过使用三元运算符确定要打印的字符串,打印出布尔标志是真还是假,如下所示 如果我使用一个C++ 在这种情况下,使用const char*或std::string更有效率的是什么,c++,stdstring,string-literals,storage-duration,C++,Stdstring,String Literals,Storage Duration,我在应用程序中使用C和C++代码的组合。 我想通过使用三元运算符确定要打印的字符串,打印出布尔标志是真还是假,如下所示 如果我使用一个常量char*,编译器在程序启动之前是否很可能将这些字符串文本“Yes”和“No”存储在一些只读内存中 如果我使用std::string,当字符串超出范围时,它会被销毁吗?但是我想编译器仍然需要将字符串文本“Yes”和“No”存储在某个地方?我不确定 bool isSet = false; // More code //std::string isSetStr
常量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 ? : %s\n", isSetStr.c_str());
printf ( "Flag is set ? : %s\n", isSetStr);
你可以用它来测试。
前者(使用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
.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)给出了以下内容:
.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
.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\u视图
,例如:
#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);
}
总之,constchar*
和string\u视图
都给出了最佳的代码string\u视图
比const char*
要多输入一些代码。
std::string
是用来操作字符串内容的,所以这里的操作过于繁琐,导致代码效率低下
string\u视图
的另一个备注:它不保证字符串以NUL结尾。在本例中,它是,因为它是从以NUL结尾的静态字符串构建的。对于一般的string_视图
使用printf
,请使用printf(“%s”、str.length()、str.data())代码>
编辑:通过禁用异常处理,可以将std::string
version减少为:
.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
.LC0:
.字符串“是”
.LC1:
.字符串“否”
.LC2:
.string“标志已设置?:%s\n”
a(布尔):
推送r12
mov eax,偏移平坦:.LC1
推动rbp
移动ebp,偏移平坦:.LC0
推送rbx
副区长,32
测试
cmove rbp,rax
lea r12,[rsp+16]
mov QWORD PTR[rsp],r12
移动rdi,rbp
打电话给斯特伦
移动rsi
mov-rdi,r12
lea rdx,[rbp+0+rax]
mov-rbx,rax
调用std::\uuucxx11::基本字符串::\u S\u复制\u字符(字符*,字符常量*,字符常量*)
mov rax,QWORD PTR[rsp]
mov QWORD PTR[rsp+8],rbx
移动edi,偏移平面:.LC2
mov字节PTR[rax+rbx],0
移动rsi,QWORD PTR[rsp]
异或eax,eax
调用printf
移动rdi,rsp
调用std::\uuuCXX11::基本字符串::\uM\uDispose()
加上rsp,32
流行音乐
流行限制性商业惯例
流行音乐r12
ret
这仍然比字符串视图的版本多得多。请注意,编译器足够聪明,可以在这里删除堆上的内存分配,但它仍然被迫计算字符串的长度(即使printf也会自己计算它)。任何一个版本都会在只读内存中分配字符串文本本身。这两个版本都使用了超出范围的局部变量,但字符串文本仍然存在,因为它们不是本地存储的
关于性能,C++容器类几乎总是比“原始”C效率低。在用G++O3测试代码时,我得到:
void test_cstr (bool isSet)
{
const char* isSetStr = isSet ? "Yes" : "No";
printf ( "Flag is set ? : %s\n", isSetStr);
}
拆卸(x86):
字符串文本被加载到只读位置,isSetStr
变量被简单地优化掉
现在使用相同的编译器和选项(-O3)尝试此操作:
拆卸(x86):
字符串文本仍然分配在只读内存中,因此部分是相同的。但是我们得到了大量开销膨胀的代码
但另一方面,在本例中,到目前为止最大的瓶颈是控制台I/O,因此其余代码的性能甚至与此无关。努力编写尽可能可读的代码,并且只在实际需要时进行优化。C语言中的手动字符串处理速度很快,但也非常容易出错和麻烦。字符串文字具有静态存储持续时间,它们在程序结束之前一直处于活动状态
请注意,如果在程序中使用相同的字符串文字,则编译器不必将该字符串文字存储为一个对象
就是这个表达
"Yes" == "Yes"
根据编译器选项,可以生成true或false。但通常默认情况下,相同的字符串文本存储为一个字符串文本
如果类型为std::string
的对象未在命名空间中声明且没有关键字static
,则该类型的对象具有自动存储持续时间。这意味着当控件传递给一个块时,这样的对象会被重新创建,并且在控件离开块时每次都被破坏。 < P>等效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());
}
冷静点
printf
将比嵌入程序源代码中的const char[]
数据中的std::string
的任何构造都要慢个数量级
检查代码性能时始终使用探查器。编写一个小程序来测试一个假设通常无法告诉你大程序中发生了什么。在您介绍的情况下,一个好的编译器将优化
int main(){printf ( "Flag is set ? : No\n");}
isSet的类型?“是”:“否”
是const char*
,与将其存储在std::string
或const char*
(或std::stringview
或…)中无关。(因此编译器对字符串文本的处理是平等的)
根据,
std::string
版本的速度慢了约6倍,这是可以理解的,因为它需要额外的动态分配
除非您需要额外的代码<代码> STD::String ,否则您可能会停留在<代码> const char */COS>。
也要考虑。而且是的,用于初始化指针的字符串、字符串或字符串视图将存储在执行文件中。
#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());
}
#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());
}
int main(){printf ( "Flag is set ? : No\n");}