如何解决在这种情况下,使用 const char* 或 std::string 更有效
我在我的应用程序中使用了 C 和 C++ 代码的组合。
我想打印一个布尔标志是真还是假,如下所示,通过使用三元运算符来确定要打印的字符串。
如果我使用 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 ? : %s\n",isSetStr.c_str());
printf ( "Flag is set ? : %s\n",isSetStr);
解决方法
任一版本都会在只读内存中分配字符串文字本身。任一版本都使用超出范围的局部变量,但字符串文字仍然存在,因为它们未存储在本地。
关于性能,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,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 ? : %s\n"
a(bool):
test dil,OFFSET FLAT:.LC0
mov esi,OFFSET FLAT:.LC1
cmove rsi,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,dil
lea rax,[rsp+16]
cmovne r12,rsi
or rcx,-1
mov rdi,r12
mov QWORD PTR [rsp],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],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,eax
jmp printf
综上所述,const char*
和 string_view
都给出了最优代码。与 string_view
相比,const char*
需要输入更多的代码。
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 ? : %s\n"
a(bool):
push r12
mov eax,OFFSET FLAT:.LC1
push rbp
mov ebp,OFFSET FLAT:.LC0
push rbx
sub rsp,dil
cmove rbp,rax
lea r12,r12
mov rdi,rbp
call strlen
mov rsi,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,OFFSET FLAT:.LC2
mov BYTE PTR [rax+rbx],rsp
call std::__cxx11::basic_string<char,std::allocator<char> >::_M_dispose()
add rsp,32
pop rbx
pop rbp
pop r12
ret
这仍然比 string_view
的版本多得多。请注意,编译器足够聪明,可以在此处删除堆上的内存分配,但它仍然被迫计算字符串的长度(即使 printf 也会自行计算)。
等效的 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());
}
https://godbolt.org/z/v3ebcsrYE
,isSet ? "Yes" : "No"
的类型是 const char*
,这与您将它存储在 std::string
或 const char*
(或 std::stringview
,或 ... )。 (所以字符串文字被编译器同等对待)。
std::string
版本慢约 6 倍,这是可以理解的,因为它需要额外的动态分配。
除非您需要 std::string
的额外功能,否则您可以继续使用 const char*
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。