在这种情况下,使用constchar*或std::string更有效



我在应用程序中使用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_viewprintf的通用用法,请使用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::stringconst 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

最新更新