0

我在我的应用程序中使用了 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);
4

6 回答 6

4

任何一个版本都会将字符串文字本身分配到只读内存中。任何一个版本都使用超出范围的局部变量,但字符串文字仍然存在,因为它们没有存储在本地。

关于性能,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, QWORD PTR [rsp]
        cmp     rdi, rbp
        je      .L5
        call    operator delete(void*)
.L5:
        mov     rdi, r12
        call    _Unwind_Resume

字符串文字仍然分配在只读内存中,因此该部分是相同的。但是我们得到了大量的开销膨胀代码。

但另一方面,在这种情况下,迄今为止最大的瓶颈是控制台 I/O,因此其余代码的性能甚至不相关。努力编写尽可能易读的代码,并仅在您真正需要时进行优化。C 中的手动字符串处理速度很快,但也非常容易出错和繁琐。

于 2021-05-21T08:23:55.667 回答
3

冷静下来!

printf将比程序源代码中嵌入的任何数据的构造慢几个数量级。std::stringconst char[]

检查代码性能时始终使用分析器。编写一个小程序来尝试检验一个假设通常无法告诉您有关您的大程序中正在发生的事情的任何信息。在您介绍的情况下,一个好的编译器将优化为

int main(){printf ( "Flag is set ? : No\n");}
于 2021-05-21T08:50:44.630 回答
3

您可以使用Godbolt对其进行测试。前者(使用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

后者(使用 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, 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 ? : %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, 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了最优代码。string_viewconst 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, 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

这仍然比string_view's 版本要多得多。请注意,编译器足够聪明,可以在这里删除堆上的内存分配,但它仍然被迫计算字符串的长度(即使 printf 也会自己计算它)。

于 2021-05-21T08:23:34.807 回答
1

字符串文字具有静态存储持续时间,它们在程序结束之前一直存在。

请注意,如果您在程序中使用相同的字符串文字,编译器不必将此字符串文字存储为一个对象。

就是这个表情

"Yes" == "Yes"

根据编译器选项,可以产生真或假。但通常默认情况下,相同的字符串文字存储为一个字符串文字。

如果类型的对象std::string没有在命名空间中声明并且没有关键字,则它们static具有自动存储持续时间。这意味着当控件被传递给块时,这样的对象会在每次控件离开块时重新创建并销毁。

于 2021-05-21T08:26:47.370 回答
0

的类型isSet ? "Yes" : "No"是,与您将其存储在或 a (or , or ...)const char*中的事实无关。(因此编译器会平等对待字符串文字)。std::stringconst char*std::stringview

quick-bench.com 报道

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

除非您需要 的额外功能,否则您std::string可能会保留const char*.

于 2021-05-21T08:56:40.747 回答
0

等效的 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 版本。

编辑:这个版本的 setup/exit 开销很小,但在调用printf.

#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

于 2021-05-21T08:32:30.810 回答