我们从 C++ 语义 → 编译器实现 → 汇编层面 来看 引用(reference) 和 指针(pointer) 的区别。
1. 语义上的区别
- 指针:显式的内存地址变量,可以指向不同对象,也可以是
nullptr。 - 引用:C++ 语义上是“别名”,一旦绑定对象就不可再更改,语法上不像指针那样显式取/解引用。
2. 编译器实现
(1)指针
编译器会生成一个真正的“指针变量”,存储对象的地址。例如:
int x = 10;
int* p = &x;
*p = 20;
对应 IR/汇编上,p 就是一个存放地址的内存单元或寄存器。
(2)引用
C++ 标准没有要求引用必须如何实现,但几乎所有主流编译器(GCC/Clang/MSVC)会把 引用当作指针来实现。
区别在于:
- 编译器对引用做了“语法糖”限制:引用一旦初始化,就不能再改变指向。
- 在优化层面,编译器甚至可能把引用直接优化掉,完全用原始变量代替。
例如:
int x = 10;
int& r = x;
r = 20;
编译器在没有优化时,通常会把 r 实现为一个指针,使用时自动解引用。优化后则可能直接用 x。
3. 汇编层面差异
我们用一个简单例子(GCC -O0 编译,x86-64 汇编):
#include <iostream>
int main() {
int a = 5;
int* p = &a;
*p = 10;
int& r = a;
r = 20;
return 0;
}
可能得到的关键汇编片段(伪简化):
mov DWORD PTR [rbp-4], 5 ; int a = 5
lea rax, [rbp-4] ; &a
mov QWORD PTR [rbp-16], rax ; p = &a
mov rax, QWORD PTR [rbp-16] ; load p
mov DWORD PTR [rax], 10 ; *p = 10
lea rax, [rbp-4] ; &a
mov QWORD PTR [rbp-24], rax ; r = a (reference)
mov rax, QWORD PTR [rbp-24] ; load reference (actually a pointer)
mov DWORD PTR [rax], 20 ; r = 20
可以看到:
- 指针 p 和 引用 r 在未优化时,实际上都存成了
&a的地址。 - 使用时 引用 r 自动解引用,编译器不会要求你写
*r,而是直接在汇编里插入解引用指令。 - 在 优化模式 -O2 下,引用常常被优化掉,变成直接对
a的操作:
mov DWORD PTR [rbp-4], 5
mov DWORD PTR [rbp-4], 10
mov DWORD PTR [rbp-4], 20
这里完全没有了 r 或指针的痕迹。
4. 总结
- 在汇编层面,引用本质上就是一个“受限制的指针” ,通常实现为存放目标地址的隐藏指针。
与指针的区别在于:
-
- 指针是程序员可见的变量;
- 引用由编译器保证“不可变绑定”,并且可能被优化掉。
- 如果开了优化,引用常常直接消失,只剩对原变量的操作。