从汇编层面看指针和引用

67 阅读2分钟

我们从 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. 总结

  • 在汇编层面,引用本质上就是一个“受限制的指针” ,通常实现为存放目标地址的隐藏指针。

与指针的区别在于:

    • 指针是程序员可见的变量;
    • 引用由编译器保证“不可变绑定”,并且可能被优化掉。
  • 如果开了优化,引用常常直接消失,只剩对原变量的操作。