You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MSVC对含字节数组的结构体生成不必要拷贝的问题咨询

关于MSVC消除不必要拷贝的问题

给定代码

#include <memory>

struct Foo
{
    alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) std::byte _value[16];
};

Foo fByRef(const Foo&);

Foo gByRef(const Foo& input) {
    const Foo intermediate1 = fByRef(input);
    const Foo intermediate2 = fByRef(intermediate1);
    return fByRef(intermediate2);
}

Foo fByVal(Foo);

Foo gByVal(Foo input) {
    const Foo intermediate1 = fByVal(input);
    const Foo intermediate2 = fByVal(intermediate1);
    return fByVal(intermediate2);
}

不同编译器的汇编输出

Clang(-O2 -DNDEBUG -std=c++17)生成的gByRef代码

sub     rsp, 40
        call    fByRef(Foo const&)@PLT
        mov     qword ptr [rsp + 16], rax
        mov     qword ptr [rsp + 24], rdx
        lea     rdi, [rsp + 16]
        call    fByRef(Foo const&)@PLT
        mov     qword ptr [rsp], rax
        mov     qword ptr [rsp + 8], rdx
        mov     rdi, rsp
        call    fByRef(Foo const&)@PLT
        add     rsp, 40
        ret

MSVC 19.33(/O2 /DNDEBUG /std:c++17)生成的gByRef代码

$T1 = 32
$T2 = 32
intermediate1$ = 48
intermediate2$ = 64
__$ArrayPad$ = 80
__$ReturnAddress$ = 112
input$ = 120
Foo gByRef(Foo const &) PROC                        ; gByRef, COMDAT
$LN4:
        push    rbx
        sub     rsp, 96                             ; 00000060H
        mov     rax, QWORD PTR __security_cookie
        xor     rax, rsp
        mov     QWORD PTR __$ArrayPad$[rsp], rax
        mov     rbx, rcx
        lea     rcx, QWORD PTR $T2[rsp]
        call    Foo fByRef(Foo const &)       ; fByRef
        lea     rdx, QWORD PTR intermediate1$[rsp]
        lea     rcx, QWORD PTR $T1[rsp]
        movaps  xmm0, XMMWORD PTR [rax]
        movdqa  XMMWORD PTR intermediate1$[rsp], xmm0
        call    Foo fByRef(Foo const &)       ; fByRef
        lea     rdx, QWORD PTR intermediate2$[rsp]
        mov     rcx, rbx
        movaps  xmm0, XMMWORD PTR [rax]
        movdqa  XMMWORD PTR intermediate2$[rsp], xmm0
        call    Foo fByRef(Foo const &)       ; fByRef
        mov     rax, rbx
        mov     rcx, QWORD PTR __$ArrayPad$[rsp]
        xor     rcx, rsp
        call    __security_check_cookie
        add     rsp, 96                             ; 00000060H
        pop     rbx
        ret     0
Foo gByRef(Foo const &) ENDP  

MSVC并未直接将intermediate1的地址传入第二次调用,而是在栈上创建临时变量,通过xmm0寄存器拷贝结果。

Clang生成的gByVal代码

gByVal(Foo):                          # @gByVal(Foo)
        push    rax
        call    fByVal(Foo)@PLT
        mov     rdi, rax
        mov     rsi, rdx
        call    fByVal(Foo)@PLT
        mov     rdi, rax
        mov     rsi, rdx
        pop     rax
        jmp     fByVal(Foo)@PLT   

MSVC生成的gByVal代码

$T1 = 32
$T2 = 32
$T3 = 32
$T4 = 48
$T5 = 48
__$ReturnAddress$ = 80
input$ = 88
Foo gByVal(Foo) PROC                     ; gByVal, COMDAT
$LN4:
        push    rbx
        sub     rsp, 64                             ; 00000040H
        movaps  xmm0, XMMWORD PTR [rdx]
        mov     rbx, rcx
        lea     rdx, QWORD PTR $T3[rsp]
        movdqa  XMMWORD PTR $T3[rsp], xmm0
        lea     rcx, QWORD PTR $T5[rsp]
        call    Foo fByVal(Foo)                ; fByVal
        lea     rdx, QWORD PTR $T2[rsp]
        lea     rcx, QWORD PTR $T4[rsp]
        movaps  xmm0, XMMWORD PTR [rax]
        movdqa  XMMWORD PTR $T2[rsp], xmm0
        call    Foo fByVal(Foo)                ; fByVal
        lea     rdx, QWORD PTR $T1[rsp]
        mov     rcx, rbx
        movaps  xmm0, XMMWORD PTR [rax]
        movdqa  XMMWORD PTR $T1[rsp], xmm0
        call    Foo fByVal(Foo)                ; fByVal
        mov     rax, rbx
        add     rsp, 64                             ; 00000040H
        pop     rbx
        ret     0
Foo gByVal(Foo) ENDP                     ; gByVal

可以看到,Clang能借助Itanium ABI做优化,但MSVC仍插入了不必要的临时变量与拷贝操作。


问题解答

1. 如何让MSVC消除这些不必要的拷贝?

  • 启用更激进的优化选项:尝试添加/Ob3(最大化内联优化)或/Oxs(优化代码大小,部分场景下会顺带消除冗余拷贝)选项,注意这类选项可能增加编译时间、降低调试友好性。
  • 切换编译前端:使用/clang:-O2让MSVC调用Clang前端编译,直接复用Clang的优化逻辑。
  • 手动调整代码结构:
    • 将gByRef改为链式调用:return fByRef(fByRef(fByRef(input)));,减少命名中间变量,帮助优化器识别可省略的拷贝。
    • 对于gByVal,将参数改为引用传递,或利用C++17复制消除规则,让中间变量以临时对象形式传递,避免显式构造命名变量。
  • 禁用栈保护(谨慎使用):添加/GS-选项禁用栈安全检查,简化生成的代码,帮助优化器识别冗余操作,该操作存在安全风险,生产环境需评估后使用。

2. C++标准是否强制要求MSVC生成这些拷贝?

C++标准不强制要求生成这些拷贝。标准中的**复制消除(Copy Elision)**规则允许编译器省略不必要的拷贝/移动操作,即使拷贝/移动构造函数有副作用。示例代码中的中间变量intermediate1、intermediate2完全符合复制消除的条件,编译器有权省略相关拷贝。

MSVC生成冗余拷贝的原因是其默认Microsoft x64 ABI的规则与优化器实现策略:该ABI对大于8字节的对象,要求调用者分配栈空间并传递地址,而优化器在部分场景下未识别到可直接复用这些栈空间,导致额外拷贝。这是编译器实现选择,而非标准强制要求。


内容的提问来源于stack exchange,提问作者user1083696

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 05:45:31