微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决在这种情况下,使用 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::stringconst char*(或 std::stringview,或 ... )。 (所以字符串文字被编译器同等对待)。

根据quick-bench.com

std::string 版本慢约 6 倍,这是可以理解的,因为它需要额外的动态分配。

除非您需要 std::string 的额外功能,否则您可以继续使用 const char*

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。