博主介绍:程序喵大人
- 35 - 资深C/C++/Rust/Android/iOS客户端开发
- 10年大厂工作经验
- 嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手
- 《C++20高级编程》《C++23高级编程》等多本书籍著译者
- 更多原创精品文章,首发gzh,见文末
- 👇👇记得订阅专栏,以防走丢👇👇
😉C++基础系列专栏
😃C语言基础系列专栏
🤣C++大佬养成攻略专栏
🤓C++训练营
👉🏻个人网站
前两天模拟面试,问了学员一个问题:
"你觉得 C++ 的引用和指针,在汇编层面有什么区别?"
他想了一下,回答:"引用不能为空,指针可以为空。"
面试官又问:"那从汇编看呢?"
他答不出了。
这个问题的答案其实很简单:汇编层面,引用和指针完全一样。之所以要区分它们,差别在编译期的类型检查和语法保护。
一、指针与引用:汇编层完全一致
写两段代码,编译成汇编看看:
cpp
// 指针
int a = 10;
int* p = &a;
*p = 20;
// 引用
int b = 10;
int& r = b;
r = 20;
用 g++ -S 编译出汇编代码,你会发现两者生成的汇编指令几乎一模一样:都是把变量的地址存入寄存器,然后通过地址去读写。
asm
; 指针版本
movl $10, -4(%rbp) ; a = 10
leaq -4(%rbp), %rax ; 取 a 的地址
movq %rax, -16(%rbp) ; p = &a
movq -16(%rbp), %rax ; 取地址
movl $20, (%rax) ; *p = 20
; 引用版本
movl $10, -4(%rbp) ; b = 10
leaq -4(%rbp), %rax ; 取 b 的地址
movq %rax, -16(%rbp) ; r = &b(引用实际存的是地址)
movq -16(%rbp), %rax
movl $20, (%rax) ; r = 20
可以看到,引用本质就是指针的语法糖。编译器会确保引用在初始化时必须绑定到有效对象,且不能重新绑定,这些约束都是编译期完成的,运行时并不会多出任何指令。
二、对象内存布局:C 结构体 vs C++ 类

C 的 struct 只包含数据,C++ 的 class 可以包含数据、成员函数、虚函数表指针,内存布局会发生本质变化。
简单类(无虚函数)
cpp
class Point {
int x, y;
public:
void move(int dx, int dy) { x += dx; y += dy; }
};
sizeof(Point) 等于 sizeof(int) * 2。
成员函数不占对象空间,所有对象共享同一份函数代码。
带虚函数的类
cpp
class Shape {
int id;
public:
virtual void draw() {}
};
这时 sizeof(Shape) 不再是 4,而是 16 左右(64 位系统)。
多出来的是 vtable 指针。
内存布局大致如下:
text
+--------+
| vptr | --> 指向 vtable
+--------+
| id | 数据成员
+--------+
vtable 在哪里
vtable 存在只读数据段中,所有同类对象共享同一张 vtable:
text
vtable for Shape:
+--------+
| RTTI | 运行时类型信息
+--------+
| ~Shape | 虚构构函数
+--------+
| draw | 虚函数实现
+--------+
面试中的高频考点是:如果类有虚函数,构造函数中会隐式初始化 vptr。这也是为什么在构造函数里调用虚函数时,调用的是当前类的版本,而不是派生类的版本------因为此时 vptr 还没被子类覆盖。

三、new / delete vs malloc / free:底层差了两步
cpp
// C++
Obj* p = new Obj();
delete p;
// C
Obj* p = (Obj*)malloc(sizeof(Obj));
free(p);
new 的底层可以拆成两步:
- 调用
operator new分配内存(类似 malloc) - 调用构造函数
delete 也同样是两步:
- 调用析构函数
- 调用
operator delete释放内存
cpp
// new 的底层展开
void* mem = ::operator new(sizeof(Obj));
Obj* p = new (mem) Obj(); // placement new
// delete 的底层展开
p->~Obj();
::operator delete(p);
面试官经常用 malloc 构造对象来考察这一点:
cpp
Obj* p = (Obj*)malloc(sizeof(Obj));
// p 现在指向的是未初始化的原始内存,不能直接用
new (p) Obj(); // 用 placement new 在已分配内存上构造对象
四、快速对比清单
| 特性 | C | C++ | 底层差异 |
|---|---|---|---|
| 对象大小 | sizeof(struct) | sizeof(class) | C++ 可能多 vptr |
| 函数 | 全局函数 | 成员函数 | 成员函数隐式传 this |
| 内存分配 | malloc / free | new / delete | new 多一步构造 |
| 类型安全 | 强制转换随意 | 转换受限 | 编译期检查 |
| 构造 | 手动初始化 | 自动调用构造 | RAII 机制 |
面试中能说清楚"引用在汇编层就是指针"和"虚函数会引入 vptr",基本就能在这一类问题上脱颖而出。
很多人学了三年 C++,却从没用过 objdump -d 或 gdb 看对象的内存布局。真正理解底层,不是背概念,而是亲眼看到它。
码字不易,欢迎大家点赞,关注,评论,谢谢!