首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么使用三元操作符返回字符串会产生与在等效if/ the块中返回的代码大不相同?

为什么使用三元操作符返回字符串会产生与在等效if/ the块中返回的代码大不相同?
EN

Stack Overflow用户
提问于 2020-08-09 14:07:21
回答 2查看 3.8K关注 0票数 70

我当时正在玩编译器资源管理器( Compiler Explorer ),在使用以下内容时,我偶然发现了三元操作符的有趣行为:

代码语言:javascript
复制
std::string get_string(bool b)
{
    return b ? "Hello" : "Stack-overflow";
}

为此(使用-O3)生成的编译器代码如下:

代码语言:javascript
复制
get_string[abi:cxx11](bool):                 # @get_string[abi:cxx11](bool)
        push    r15
        push    r14
        push    rbx
        mov     rbx, rdi
        mov     ecx, offset .L.str
        mov     eax, offset .L.str.1
        test    esi, esi
        cmovne  rax, rcx
        add     rdi, 16 #< Why is the compiler storing the length of the string
        mov     qword ptr [rbx], rdi
        xor     sil, 1
        movzx   ecx, sil
        lea     r15, [rcx + 8*rcx]
        lea     r14, [rcx + 8*rcx]
        add     r14, 5 #< I also think this is the length of "Hello" (but not sure)
        mov     rsi, rax
        mov     rdx, r14
        call    memcpy #< Why is there a call to memcpy
        mov     qword ptr [rbx + 8], r14
        mov     byte ptr [rbx + r15 + 21], 0
        mov     rax, rbx
        pop     rbx
        pop     r14
        pop     r15
        ret
.L.str:
        .asciz  "Hello"

.L.str.1:
        .asciz  "Stack-Overflow"

但是,编译器为下面的代码段生成的代码要小得多,不需要调用memcpy,也不关心同时知道两个字符串的长度。有两个不同的标签可以跳转到

代码语言:javascript
复制
std::string better_string(bool b)
{
    if (b)
    {
        return "Hello";
    }
    else
    {
        return "Stack-Overflow";
    }
}

上面的代码段(使用-O3的clang主干)的编译器生成的代码如下:

代码语言:javascript
复制
better_string[abi:cxx11](bool):              # @better_string[abi:cxx11](bool)
        mov     rax, rdi
        lea     rcx, [rdi + 16]
        mov     qword ptr [rdi], rcx
        test    sil, sil
        je      .LBB0_2
        mov     dword ptr [rcx], 1819043144
        mov     word ptr [rcx + 4], 111
        mov     ecx, 5
        mov     qword ptr [rax + 8], rcx
        ret
.LBB0_2:
        movabs  rdx, 8606216600190023247
        mov     qword ptr [rcx + 6], rdx
        movabs  rdx, 8525082558887720019
        mov     qword ptr [rcx], rdx
        mov     byte ptr [rax + 30], 0
        mov     ecx, 14
        mov     qword ptr [rax + 8], rcx
        ret

同样的结果是,当我使用三元运算符时:

代码语言:javascript
复制
std::string get_string(bool b)
{
    return b ? std::string("Hello") : std::string("Stack-Overflow");
}

我想知道为什么第一个示例中的三元操作符生成编译器代码。我相信罪魁祸首在const char[]内部。

P.S: GCC在第一个例子中确实调用了strlen,但Clang没有。

链接到编译器资源管理器示例:https://godbolt.org/z/Exqs6G

谢谢您抽时间见我!

抱歉,代码墙

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-08-09 15:37:34

这里最重要的区别是,第一个版本是无分支

16不是这里任何字符串的长度(较长的字符串只有15字节长);它是返回对象的偏移量(其地址在RDI中传递以支持RVO),用于指示正在使用的小字符串优化(注意缺少分配)。长度为5或5+1+8,存储在R14中,存储在std::string中,并传递给memcpy (以及CMOVNE选择的指针)以加载实际的字符串字节。

另一个版本有一个明显的分支(尽管std::string构造的一部分已经悬挂在它上面),并且实际上有5和14显式,但是字符串字节被包含为不同大小的即时值(表示为整数)这一事实而混淆了。

至于为什么这三个等价的函数产生两个不同版本的生成代码,我所能提供的就是优化器是迭代和启发式算法;它们不能独立于它们的起点可靠地找到相同的“最佳”程序集。

票数 60
EN

Stack Overflow用户

发布于 2020-08-10 19:15:55

第一个版本返回一个string对象,该对象使用一个非常量表达式初始化,产生一个字符串文本,因此构造函数将像运行任何其他变量字符串对象一样运行,从而运行memcpy来进行初始化。

其他变体返回使用字符串文字初始化的一个字符串对象或用另一个字符串文本初始化的另一个字符串对象,这两个字符串对象都可以优化为由不需要memcpy的常量表达式构造的字符串对象。

因此,真正的答案是:第一个版本在初始化对象之前对char[]表达式操作?:运算符,在已经初始化的字符串对象上操作其他版本。

一个版本是否是无分支并不重要。

票数 13
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63327288

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档