内容会比较粗略,且需些许汇编的知识。
指针和引用的区别在哪?
学过C++的同学都能答上几点:
- 引用是别名
- 引用必须初始化,并且初始化后不能重新引用其他变量
- 指针是所指内容的地址
但引用是别名,这是C++语法规定的。
那引用到底在汇编层面和指针有什么区别呢?
无区别
这是我当时自己观察各种相关资料得出的结论,引用会被C++ 编译器当做const指针来进行操作。
来看个例子:
汇编打开引用的面具
先分别用指针、引用来写个函数swap
//指针版
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
//引用版
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
可以利用 gcc -S 输出汇编:
引用版汇编
__Z4swapRiS_: ## @_Z4swapRiS_
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movq %rdi, -8(%rbp) # 传入的第一个参数存放到%rbp-8 (应该是采用的寄存器传参,而不是常见的压栈)
movq %rsi, -16(%rbp) # 第二个参数 存放到 %rbp-16
movq -8(%rbp), %rsi # 第一个参数赋给 rsi
movl (%rsi), %eax # 以第一个参数为地址取出值赋给eax,取出*a暂存寄存器
movl %eax, -20(%rbp) # temp = a
movq -16(%rbp), %rsi # 将第二个参数重复上面的
movl (%rsi), %eax
movq -8(%rbp), %rsi
movl %eax, (%rsi) # a = b
movl -20(%rbp), %eax # eax = temp
movq -16(%rbp), %rsi
movl %eax, (%rsi) # b = temp
popq %rbp
retq
.cfi_endproc
## -- End function
再来一个函数调用引用版本swap
void call()
{
int a = 10;
int b = 3;
int &ra = a;
int &rb = b;
swap(ra, rb);
}
对应的汇编:
__Z4callv: ## @_Z4callv
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $32, %rsp
leaq -8(%rbp), %rax # rax中是b的地址
leaq -4(%rbp), %rcx # rcx中是a的地址
movl $10, -4(%rbp)
movl $3, -8(%rbp) # 分别初始化a、b
movq %rcx, -16(%rbp) # 赋给ra引用
movq %rax, -24(%rbp) # 赋给rc引用
movq -16(%rbp), %rdi # 寄存器传参, -16(%rbp)就是rcx中的值也就是a的地址
movq -24(%rbp), %rsi # 略
callq __Z4swapRiS_
addq $32, %rsp
popq %rbp
retq
从上面可以看到给引用赋初值,就是把所引用对象的地址赋给引用所在内存,和指针是一样的。
再看指针的汇编
指针版汇编
__Z4swapPiS_: ## @_Z4swapPiS_
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movq %rdi, -8(%rbp)
movq %rsi, -16(%rbp)
movq -8(%rbp), %rsi
movl (%rsi), %eax
movl %eax, -20(%rbp)
movq -16(%rbp), %rsi
movl (%rsi), %eax
movq -8(%rbp), %rsi
movl %eax, (%rsi)
movl -20(%rbp), %eax
movq -16(%rbp), %rsi
movl %eax, (%rsi)
popq %rbp
retq
.cfi_endproc
## -- End function
真是完全一样,并不是直接复制的引用汇编,而是真的在编译器实现上都是相同的方式。
指针版调用
void pointer_call()
{
int a = 10;
int b = 3;
int *pa = &a;
int *pb = &b;
swap(pa, pb);
}
改了函数名,对应汇编:
__Z12pointer_callv: ## @_Z12pointer_callv
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $32, %rsp
leaq -8(%rbp), %rax
leaq -4(%rbp), %rcx
movl $10, -4(%rbp)
movl $3, -8(%rbp)
movq %rcx, -16(%rbp)
movq %rax, -24(%rbp)
movq -16(%rbp), %rdi
movq -24(%rbp), %rsi
callq __Z4swapPiS_
addq $32, %rsp
popq %rbp
retq
也几乎完全是一样。