初学引用时,往往很难真正理解引用,它与指针究竟有什么区别和联系。下面我们不妨看看编译器如何理解引用和指针的。
一.函数通过指针传参
1.1 示例代码
#include <iostream>
using namespace std;
void swap(int *x,int *y)//指针传参
{
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
int main(int argc, char** argv)
{
int a = 20;
int b = 30;
swap(&a,&b);//指针传参
return 0;
}
1.2 汇编代码
1.2.1 main函数汇编代码
<+0>: push %rbp
<+1>: mov %rsp,%rbp
<+4>: sub $0x30,%rsp
<+8>: mov %ecx,0x10(%rbp)
<+11>: mov %rdx,0x18(%rbp)
<+15>: callq 0x40e780 <__main>
<+20>: movl $0x14,-0x4(%rbp)//栈中偏移4的空间初始化为20,即int a = 20
<+27>: movl $0x1e,-0x8(%rbp)//栈中偏移8的空间初始化为30,即int b = 30
<+34>: lea -0x8(%rbp),%rdx//取b的地址传给rdx
<+38>: lea -0x4(%rbp),%rax//取a的地址传给rax
<+42>: mov %rax,%rcx//rax的值传给rcx,即a的地址保存到rcx中
<+45>: callq 0x401530 <swap(int*, int*)>//引用传参的函数
<+50>: mov $0x0,%eax
<+55>: add $0x30,%rsp
<+59>: pop %rbp
<+60>: retq
由上图可知:
(1)main函数在栈中开辟了两个4字节空间,分别分配给a和b,并且分别初始化为20和30
<+20>: movl $0x14,-0x4(%rbp)//栈中偏移4的空间初始化为20,即int a = 20
<+27>: movl $0x1e,-0x8(%rbp)//栈中偏移8的空间初始化为30,即int b = 30
(2)准备传给swap函数的参数
<+34>: lea -0x8(%rbp),%rdx//取b的地址传给rdx
<+38>: lea -0x4(%rbp),%rax//取a的地址传给rax
<+42>: mov %rax,%rcx //rax的值传给rcx,即a的地址保存到rcx中
<+45>: callq 0x401530 <swap(int&, int&)>//引用传参的函数
swap函数的参数分别保存在rcx和rdx中,且是参数地址。
1.2.2 swap函数汇编代码
<+0>: push %rbp
<+1>: mov %rsp,%rbp
<+4>: sub $0x10,%rsp
<+8>: mov %rcx,0x10(%rbp)//a的地址保存到栈中(对应形参x)
<+12>: mov %rdx,0x18(%rbp)//b的地址保存到栈中(对应形参y)
<+16>: mov 0x10(%rbp),%rax//取出a的地址到rax中
<+20>: mov (%rax),%eax//a的值赋给eax(4字节)
<+22>: mov %eax,-0x4(%rbp)//eax的值赋给栈偏移为4的变量tmp中,即tmp = a
<+25>: mov 0x18(%rbp),%rax//取出b的地址到rax中
<+29>: mov (%rax),%edx//b的值赋给edx(4字节)
<+31>: mov 0x10(%rbp),%rax//取出a的地址到rax中
<+35>: mov %edx,(%rax)//eax的值赋给a,即a = b
<+37>: mov 0x18(%rbp),%rax//取出b的地址到rax中
<+41>: mov -0x4(%rbp),%edx//edx = tmp
<+44>: mov %edx,(%rax) //b = edx,即b = tmp
<+46>: add $0x10,%rsp
<+50>: pop %rbp
<+51>: retq
由上图可知:
(1)swap函数接收了a和b变量的地址
<+8>: mov %rcx,0x10(%rbp)//a的地址保存到栈中(对应形参x)
<+12>: mov %rdx,0x18(%rbp)//b的地址保存到栈中(对应形参y)
(2)直接交换a、b空间的值
<+16>: mov 0x10(%rbp),%rax//取出a的地址到rax中
<+20>: mov (%rax),%eax//a的值赋给eax(4字节)
<+22>: mov %eax,-0x4(%rbp)//eax的值赋给栈偏移为4的变量tmp中,即tmp = a
<+25>: mov 0x18(%rbp),%rax//取出b的地址到rax中
<+29>: mov (%rax),%edx//b的值赋给edx(4字节)
<+31>: mov 0x10(%rbp),%rax//取出a的地址到rax中
<+35>: mov %edx,(%rax)//eax的值赋给a,即a = b
<+37>: mov 0x18(%rbp),%rax//取出b的地址到rax中
<+41>: mov -0x4(%rbp),%edx//edx = tmp
<+44>: mov %edx,(%rax) //b = edx,即b = tmp
二.函数通过引用传参
2.1 示例代码
#include <iostream>
using namespace std;
void swap(int &x,int &y)//引用传参
{
int tmp;
tmp = x;
x = y;
y = tmp;
}
int main(int argc, char** argv)
{
int a = 20;
int b = 30;
swap(a,b);//引用传参
return 0;
}
2.2 汇编代码
2.2.1 main函数汇编代码
<+0>: push %rbp
<+1>: mov %rsp,%rbp
<+4>: sub $0x30,%rsp
<+8>: mov %ecx,0x10(%rbp)
<+11>: mov %rdx,0x18(%rbp)
<+15>: callq 0x40e780 <__main>
<+20>: movl $0x14,-0x4(%rbp)//栈中偏移4的空间初始化为20,即int a = 20
<+27>: movl $0x1e,-0x8(%rbp)//栈中偏移8的空间初始化为30,即int b = 30
<+34>: lea -0x8(%rbp),%rdx//取b的地址传给rdx
<+38>: lea -0x4(%rbp),%rax//取a的地址传给rax
<+42>: mov %rax,%rcx //rax的值传给rcx,即a的地址保存到rcx中
<+45>: callq 0x401530 <swap(int&, int&)>//引用传参的函数
<+50>: mov $0x0,%eax
<+55>: add $0x30,%rsp
<+59>: pop %rbp
<+60>: retq
由上图可知:
(1)main函数在栈中开辟了两个4字节空间,分别分配给a和b,并且分别初始化为20和30
<+20>: movl $0x14,-0x4(%rbp)//栈中偏移4的空间初始化为20,即int a = 20
<+27>: movl $0x1e,-0x8(%rbp)//栈中偏移8的空间初始化为30,即int b = 30
(2)准备传给swap函数的参数
<+34>: lea -0x8(%rbp),%rdx//取b的地址传给rdx
<+38>: lea -0x4(%rbp),%rax//取a的地址传给rax
<+42>: mov %rax,%rcx //rax的值传给rcx,即a的地址保存到rcx中
<+45>: callq 0x401530 <swap(int&, int&)>//引用传参的函数
swap函数的参数分别保存在rcx和rdx中,且是参数地址。
2.2.2 swap函数汇编代码
<+0>: push %rbp
<+1>: mov %rsp,%rbp
<+4>: sub $0x10,%rsp
<+8>: mov %rcx,0x10(%rbp)//a的地址保存到栈中(对应形参x)
<+12>: mov %rdx,0x18(%rbp)//b的地址保存到栈中(对应形参y)
<+16>: mov 0x10(%rbp),%rax//取出a的地址到rax中
<+20>: mov (%rax),%eax//a的值赋给eax(4字节)
<+22>: mov %eax,-0x4(%rbp)//eax的值赋给栈偏移为4的变量tmp中,即tmp = a
<+25>: mov 0x18(%rbp),%rax//取出b的地址到rax中
<+29>: mov (%rax),%edx//b的值赋给edx(4字节)
<+31>: mov 0x10(%rbp),%rax//取出a的地址到rax中
<+35>: mov %edx,(%rax)//eax的值赋给a,即a = b
<+37>: mov 0x18(%rbp),%rax//取出b的地址到rax中
<+41>: mov -0x4(%rbp),%edx//edx = tmp
<+44>: mov %edx,(%rax) //b = edx,即b = tmp
<+46>: add $0x10,%rsp
<+50>: pop %rbp
<+51>: retq
由上图可知:
(1)swap函数接收了a和b变量的地址
<+8>: mov %rcx,0x10(%rbp)//a的地址保存到栈中(对应形参x)
<+12>: mov %rdx,0x18(%rbp)//b的地址保存到栈中(对应形参y)
(2)直接交换a、b空间的值
<+16>: mov 0x10(%rbp),%rax//取出a的地址到rax中
<+20>: mov (%rax),%eax//a的值赋给eax(4字节)
<+22>: mov %eax,-0x4(%rbp)//eax的值赋给栈偏移为4的变量tmp中,即tmp = a
<+25>: mov 0x18(%rbp),%rax//取出b的地址到rax中
<+29>: mov (%rax),%edx//b的值赋给edx(4字节)
<+31>: mov 0x10(%rbp),%rax//取出a的地址到rax中
<+35>: mov %edx,(%rax)//eax的值赋给a,即a = b
<+37>: mov 0x18(%rbp),%rax//取出b的地址到rax中
<+41>: mov -0x4(%rbp),%edx//edx = tmp
<+44>: mov %edx,(%rax) //b = edx,即b = tmp
三.汇编对比
3.1 main函数对比
如下图所示。除了swap函数的形式不同,其他完全一样。
3.2 swap函数对比
如下图所示。它们的代码完全一样。
四.结论
(1)引用传参和指针传参的汇编实现是一样的。
(2)引用也是传递变量指针给swap函数。
(3)引用只是C++语言的语法糖,它本质上还是指针。