从汇编的角度揭秘C++引用,豁然开朗
C++中的引用是指已有对象的别名,可以通过该别名访问并修改被引用的对象。那么其背后的原理是什么呢?引用是否会带来额外的开销呢?我们从一段代码入手,来分析一下引用的本质。
#include <stdio.h>
int main()
{int a = 10;int &b = a;b = 11;return 0;
}
如上所示:定义了一个int型变量a,以及一个int型的引用b,并指向a。最后将b的值修改为11。
我们来看一下它对应的汇编代码:
1 pushq %rbp2 movq %rsp, %rbp //rbp = rsp 3 movl $10, -12(%rbp) //*(rbp-12) = 104 leaq -12(%rbp), %rax //rax = rbp - 125 movq %rax, -8(%rbp) //*(rbp - 8) = rax6 movq -8(%rbp), %rax //rax = *(rbp - 0x8)7 movl $11, (%rax) //*rax = 11;8 movl $0, %eax9 popq %rbp10 ret
我们来逐行分析:
先来介绍两个寄存器:
rsp:栈顶指针寄存器,rbp:栈基址指针寄存器。前两行push %rbp,以及move %rsp, %rbp。是保存函数调用之前的上下文信息,以及让rbp等于rsp。即此时main函数的栈空间是空的,这两条指令相当于main函数的准备工作。此时main函数的调用栈如下所示:
接下来:movl $10, -12(%rbp),是将一个立即数写入rbp-12这个地址中,写入栈空间如下所示:
通过对比源代码,发现这条指令对应的是这行:int a = 10。接下来leaq -12(%rbp), %rax,是将rbp-12的地址加载进rax, 即rax = rbp-12。也就是变量a的地址。接下来movq %rax, -8(%rbp),即将rax(rax为rbp-12)写入rbp-8这个地址。执行完这条之后,栈上的分布如下:
即将rbp-12存入rbp-8这个地址中。rbp-12即是a的地址。这里其实就是引用的一个实现。接下来:movq -8(%rbp), %rax,即将rbp-8地址里的内容(即rbp-12)赋值给rax, 即rax = rbp-12。接下来:movl $11, (%rax) ,即将立即数11赋值给rax所指地址。rax为rbp-12, 那么rbp-12这个地址中的内容将被赋值为11。rbp-12即a的地址,那么执行完这条指令之后,a的值将被更新为11。栈上的分布如下:
对应源码即是b=11。此时,已经完成了通过引用将被引用对像值更新的过程。由此,我们可以看出,C++中引用实现的本质就是指针,即通过存储被引用对象的地址来实现。但是它比指针更加安全,可以认为是一个指针的语法糖。
此文对应的B栈视频如下:
用汇编揭秘C++引用的本质,豁然开朗_哔哩哔哩_bilibili