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

向非引用参数传递右值,编译器为何无法省略拷贝?(附代码示例)

为什么向非引用参数传递右值时编译器无法省略拷贝?

先看你给出的代码示例:

struct Big { int a[8]; };
void foo(Big a);
Big getStuff();
void test1() { foo(getStuff()); }

用clang 6.0.0在x86_64 Linux(遵循System V ABI)下以-O3 -march=broadwell编译后,test1()的汇编代码是:

# @test1()
sub rsp, 72
lea rdi, [rsp + 40]
call getStuff()
vmovups ymm0, ymmword ptr [rsp + 40]
vmovups ymmword ptr [rsp], ymm0
vzeroupper
call foo(Big)
add rsp, 72
ret

咱们一步步拆解为什么这个拷贝没法省略:

1. 先搞懂System V ABI对大对象返回和参数传递的规则

对于x86_64的System V ABI来说:

  • 大对象返回:当返回的结构体大小超过16字节时(这里Big是32字节,刚好符合),调用者(也就是test1)需要提前在栈上给返回值分配一块空间,然后把这块空间的地址通过rdi寄存器传给被调用函数(getStuff),让getStuff直接把对象构造到这块空间里——你看汇编里的lea rdi, [rsp + 40]就是干这个的,rsp+40就是给getStuff返回值留的空间。
  • 大对象参数传递:同样,当函数的参数是超过16字节的结构体时,调用者需要在自己的栈上给参数分配一块专属空间,然后把对象拷贝到这块空间里,再调用函数。foo的参数a是Big类型,所以test1在栈上rsp开始的位置给它留了32字节的空间。

2. 两个空间不重合,拷贝避不开

这里的核心矛盾是:getStuff返回值的空间(rsp+40)和foo参数的空间(rsp)是完全分开的,而且ABI规则要求foo的参数必须在指定的栈位置上。编译器没办法让getStuff直接把对象构造到foo的参数空间里——因为test1作为调用者,得先为getStuff的返回值分配空间,再为foo的参数分配空间,这两块栈空间的布局是ABI规定死的,不能随便重叠或者复用。

所以你看到的那两条vmovups指令,就是把getStuff构造好的对象从返回值空间拷贝到foo的参数空间里,这一步是ABI强制要求的,编译器没法省略。

3. 怎么才能避免这个拷贝?

有几个可行的办法:

  • 把foo的参数改成const引用:void foo(const Big& a),这样参数传递的是指针,不需要拷贝整个对象,直接传rsp+40的地址给foo就行。
  • 如果是C++11及以后,就算用值传递,也可以给Big加个非平凡的移动构造函数——不过对于Big这种平凡类型,移动和拷贝的汇编代码是一样的,没法优化,但对于有堆内存的类型,移动会比拷贝高效很多。
  • 调整代码结构,比如让foo返回一个Big对象,然后利用返回值优化(RVO)来避免拷贝。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 04:06:04