从内存/汇编角度看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 的底层可以拆成两步:

  1. 调用 operator new 分配内存(类似 malloc)
  2. 调用构造函数

delete 也同样是两步:

  1. 调用析构函数
  2. 调用 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 -dgdb 看对象的内存布局。真正理解底层,不是背概念,而是亲眼看到它。

码字不易,欢迎大家点赞,关注,评论,谢谢!

相关推荐
是星辰吖~1 小时前
X86反汇编_深度学习阶段_1
汇编
晚风吹红霞1 小时前
C++ vector 深度剖析:从入门到模拟实现,避开所有坑
开发语言·c++
不会C语言的男孩1 小时前
C++ Primer 第5章:语句
开发语言·c++
Swift社区2 小时前
OpenHarmony鸿蒙PC平台移植 gifsicle:CC++ 三方库适配实践(Lycium tpc_c_cplusplus)
c语言·c++·harmonyos
basketball6162 小时前
C++进阶:1. 引用折叠规则
java·开发语言·c++
東隅已逝,桑榆非晚2 小时前
编译和链接
c语言·笔记
酬勤-人间道2 小时前
VTK 与 Cesium-native 结合实践:小场景三维编辑 + 数字地球精准贴合
c++·qt·vtk·遥感·岩土·cesium-native
智者知已应修善业2 小时前
【51单片机8个LED的花样12亮34熄56间隔78闪烁3秒3闪烁】2023-11-4
c++·经验分享·笔记·算法·51单片机
初中就开始混世的大魔王2 小时前
5 Fast DDS-Discovery
网络·c++·算法·中间件