C++ 传值 传地址 传引用 的汇编解释

在C语言中,函数的参数传递有两种类型:传值和传指针。

到了C++中,增加传引用的类型,但在机器执行的时候,传指针和传引用的效果是一样的,可以通过下面的例子来验证一下:

传指针与传引用demo

//demo.cpp

long long foo_p(long long *p) { //利用指针传递参数
  (*p)++;
  long long b = *p;
  return b;
}

long long foo_r(long long &a) { //利用引用传递参数
  a++;
  long long b = a;
  return b;
}

int main() {

  long long a = 3L;
  foo_p(&a); 
  foo_r(a);
  return 0;
}

通过下面的指令,得到其汇编结果:

g++ demo.cpp -o demo -g
objdump -DC demo > demo.txt

在demo.txt里找到如下片段:

//传指针的函数定义
00000000000011a8 <foo_p(long long*)>:
11a8: f3 0f 1e fa           endbr64
11ac: 55                    push   %rbp
11ad: 48 89 e5              mov    %rsp,%rbp
11b0: 48 89 7d e8           mov    %rdi,-0x18(%rbp)
11b4: 48 8b 45 e8           mov    -0x18(%rbp),%rax
11b8: 48 8b 00              mov    (%rax),%rax
11bb: 48 8d 50 01           lea    0x1(%rax),%rdx
11bf: 48 8b 45 e8           mov    -0x18(%rbp),%rax
11c3: 48 89 10              mov    %rdx,(%rax)
11c6: 48 8b 45 e8           mov    -0x18(%rbp),%rax
11ca: 48 8b 00              mov    (%rax),%rax
11cd: 48 89 45 f8           mov    %rax,-0x8(%rbp)
11d1: 48 8b 45 f8           mov    -0x8(%rbp),%rax
11d5: 5d                    pop    %rbp
11d6: c3                    retq
//传引用的函数定义
00000000000011d7 <foo_r(long long&)>:
11d7: f3 0f 1e fa           endbr64
11db: 55                    push   %rbp
11dc: 48 89 e5              mov    %rsp,%rbp
11df: 48 89 7d e8           mov    %rdi,-0x18(%rbp)
11e3: 48 8b 45 e8           mov    -0x18(%rbp),%rax
11e7: 48 8b 00              mov    (%rax),%rax
11ea: 48 8d 50 01           lea    0x1(%rax),%rdx
11ee: 48 8b 45 e8           mov    -0x18(%rbp),%rax
11f2: 48 89 10              mov    %rdx,(%rax)
11f5: 48 8b 45 e8           mov    -0x18(%rbp),%rax
11f9: 48 8b 00              mov    (%rax),%rax
11fc: 48 89 45 f8           mov    %rax,-0x8(%rbp)
1200: 48 8b 45 f8           mov    -0x8(%rbp),%rax
1204: 5d                    pop    %rbp
1205: c3                    retq


0000000000001235 <main>:
1235: f3 0f 1e fa           endbr64
1239: 55                    push   %rbp
123a: 48 89 e5              mov    %rsp,%rbp
123d: 48 83 ec 10           sub    $0x10,%rsp
1241: 64 48 8b 04 25 28 00  mov    %fs:0x28,%rax
1248: 00 00
124a: 48 89 45 f8           mov    %rax,-0x8(%rbp)
124e: 31 c0                 xor    %eax,%eax
1250: 48 c7 45 f0 03 00 00  movq   $0x3,-0x10(%rbp)
1257: 00
//传指针调用
1258: 48 8d 45 f0           lea    -0x10(%rbp),%rax
125c: 48 89 c7              mov    %rax,%rdi
125f: e8 44 ff ff ff        callq  11a8 <foo_p(long long*)>
//传引用调用
1264: 48 8d 45 f0           lea    -0x10(%rbp),%rax
1268: 48 89 c7              mov    %rax,%rdi
126b: e8 67 ff ff ff        callq  11d7 <foo_r(long long&)>

1270: b8 00 00 00 00        mov    $0x0,%eax
1275: 48 8b 55 f8           mov    -0x8(%rbp),%rdx
1279: 64 48 33 14 25 28 00  xor    %fs:0x28,%rdx

从上面的片段可以看出,无论是函数定义的地方,还是函数调用的地方,传地址和传引用在汇编层面是完全一样的,没有任何差别

其实,传指针和传引用都可以认为是传地址,体现在汇编上就是调用方在压栈的时候,压入的是某个地址,在上面的例子中,使用汇编指令lea实现的,lea将-0x10(%rbp)对应的地址得到,最终放入rdi寄存器中。被调用方则通过写这个地址,来实现“改变”形参的效果。

下面可以把传值也加上,但看结果之前,我们可以猜测,里面是没有lea这个指令的:

  1 long long foo_v(long long a) {
  2   a++;
  3   long long b = a;
  4   return b;
  5 }

同样的方式的,得到下面的汇编片段:

 0000000000001129 <foo_v(long long)>:
     1129: f3 0f 1e fa           endbr64
     112d: 55                    push   %rbp
     112e: 48 89 e5              mov    %rsp,%rbp
     1131: 48 89 7d e8           mov    %rdi,-0x18(%rbp)
     1135: 48 83 45 e8 01        addq   $0x1,-0x18(%rbp)
     113a: 48 8b 45 e8           mov    -0x18(%rbp),%rax
     113e: 48 89 45 f8           mov    %rax,-0x8(%rbp)
     1142: 48 8b 45 f8           mov    -0x8(%rbp),%rax
     1146: 5d                    pop    %rbp
     1147: c3                    retq


 00000000000011d5 <main>:
     11d5: f3 0f 1e fa           endbr64
     11d9: 55                    push   %rbp
     11da: 48 89 e5              mov    %rsp,%rbp
     11dd: 48 83 ec 10           sub    $0x10,%rsp
     11e1: 48 c7 45 f8 03 00 00  movq   $0x3,-0x8(%rbp)
     11e8: 00
     11e9: 48 8b 45 f8           mov    -0x8(%rbp),%rax
     11ed: 48 89 c7              mov    %rax,%rdi
     11f0: e8 34 ff ff ff        callq  1129 <foo_v(long long)>

果然,上面的lea变成了mov,在函数里面,操作也简单了很多。

上面这些东西,其实只要记住传指针和传引用,效果完全一样就可以了。

到了C++11,又引入了右值引用,右值引用本质上还是一个引用,所有其对应的底层操作也是一样的,再来试一下:

 00000000000011c6 <foo_rr(long long&&)>:
     11c6: f3 0f 1e fa           endbr64
     11ca: 55                    push   %rbp
     11cb: 48 89 e5              mov    %rsp,%rbp
     11ce: 48 89 7d e8           mov    %rdi,-0x18(%rbp)
     11d2: 48 8b 45 e8           mov    -0x18(%rbp),%rax
     11d6: 48 8b 00              mov    (%rax),%rax
     11d9: 48 8d 50 01           lea    0x1(%rax),%rdx
     11dd: 48 8b 45 e8           mov    -0x18(%rbp),%rax
     11e1: 48 89 10              mov    %rdx,(%rax)
     11e4: 48 8b 45 e8           mov    -0x18(%rbp),%rax
     11e8: 48 8b 00              mov    (%rax),%rax
     11eb: 48 89 45 f8           mov    %rax,-0x8(%rbp)
     11ef: 48 8b 45 f8           mov    -0x8(%rbp),%rax
     11f3: 5d                    pop    %rbp
     11f4: c3                    retq


00000000000011f5 <main>:
     11f5: f3 0f 1e fa           endbr64
     11f9: 55                    push   %rbp
     11fa: 48 89 e5              mov    %rsp,%rbp
     11fd: 48 83 ec 10           sub    $0x10,%rsp
     1201: 64 48 8b 04 25 28 00  mov    %fs:0x28,%rax
     1208: 00 00
     120a: 48 89 45 f8           mov    %rax,-0x8(%rbp)
     120e: 31 c0                 xor    %eax,%eax
     1210: 48 c7 45 f0 03 00 00  movq   $0x3,-0x10(%rbp)
     1217: 00
     1218: 48 8d 45 f0           lea    -0x10(%rbp),%rax
     121c: 48 89 c7              mov    %rax,%rdi
     121f: e8 23 00 00 00        callq  1247 <std::remove_reference<long long&>::type&& std::move<long long&>( long long&)>
     1224: 48 89 c7              mov    %rax,%rdi
     1227: e8 9a ff ff ff        callq  11c6 <foo_rr(long long&&)>

比较左值引用和右值引用的代码,可以发现,函数内部的代码完全一样,而唯一不同的在调用函数的地方,在调用之前,多了一个remove_reference的函数调用,这个其实就是std::move的作用,它用于生成一个右值引用。

综合来看,其实函数参数传递只有传值和传址两种方式。哪怕C++后面引入了函数模板,通用引用,类型推导等这些高级概念,但等到了汇编阶段,就只能二选一了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值