📚 目录
- C/C++ 内存结构总览
- 栈(Stack)vs 堆(Heap)本质区别
- 全局区、常量区、代码区详解
- C语言内存分配函数:
malloc
/calloc
/realloc
/free
- C++内存运算符:
new
/delete
背后的秘密 - 对象模型:成员变量、虚函数表布局剖析
- 拷贝构造 / 赋值运算 / 析构完整内存生命周期
- 构造/析构顺序与内存布局(含继承、虚继承)
- 内存对齐、填充与 sizeof
- 自定义内存管理:内存池、operator new 重载
- 常见内存错误与调试技巧
- 总结:现代 C++ 内存安全利器(RAII、智能指针)
🧠 1. C/C++ 内存结构总览
程序运行时的内存五大区:
区域 | 内容 |
---|---|
栈区 | 局部变量、函数参数、返回地址(自动释放) |
堆区 | 程序动态分配的内存,需要手动释放 |
全局区 | 全局变量、静态变量(程序运行期间存在) |
常量区 | 字符串字面量、const 全局变量等 |
代码区 | 存储可执行代码指令,函数体机器码 |
🧮 2. 栈 vs 堆
比较项 | 栈 | 堆 |
---|---|---|
管理方式 | 编译器自动管理 | 程序员手动管理 |
生命周期 | 离开作用域自动释放 | 手动释放(free /delete ) |
分配速度 | 快(内存顺序增长) | 慢(维护空闲链表、碎片管理) |
内存限制 | 通常较小(如 1MB) | 受限于系统物理/虚拟内存 |
⚙️ 3. 全局区 / 常量区 / 代码区
const char* s1 = "hello"; // 存储在常量区
char s2[] = "hello"; // s2 本身在栈区,内容复制到栈上
字符串字面量共用,const 字符串不可修改!
🛠️ 4. C语言的内存分配函数
void* malloc(size_t size)
void* calloc(size_t count, size_t size)
:初始化为0void* realloc(void* ptr, size_t new_size)
:扩容/收缩void free(void* ptr)
💣 常见错误:
- 忘记释放
malloc
free
释放未分配内存或释放两次
🔍 5. C++ 的 new/delete 背后机制
MyClass* p = new MyClass(); // 调用 operator new + 构造函数
delete p; // 调用析构函数 + operator delete
内部步骤:
operator new
分配内存(底层是malloc
)- 调用构造函数
delete
:先调用析构,再释放内存(底层是free
)
你可以自定义 operator new
和 delete
:
void* operator new(size_t size) {
std::cout << "Custom new\n";
return malloc(size);
}
void operator delete(void* p) {
std::cout << "Custom delete\n";
free(p);
}
🧩 6. C++ 对象模型与内存布局
示例:
class A {
int a;
virtual void f() {}
};
对象在内存中的布局如下:
- 第一部分是 vptr:指向虚函数表(vtable)
- 紧接着是成员变量(如 int a)
虚函数表是一个指针数组,存放指向虚函数的地址。
🔁 7. 拷贝构造、赋值运算、析构函数
class MyClass {
public:
MyClass(); // 构造
MyClass(const MyClass& other); // 拷贝构造
MyClass& operator=(const MyClass&); // 赋值
~MyClass(); // 析构
};
生命周期顺序:构造 → 拷贝 / 赋值 → 析构
构造/析构时机是管理资源(如内存、文件句柄)的关键。
🧱 8. 继承与内存布局:构造/析构顺序
class Base {
public:
Base() { std::cout << "Base\n"; }
virtual ~Base() { std::cout << "Destruct Base\n"; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived\n"; }
~Derived() { std::cout << "Destruct Derived\n"; }
};
构造顺序:Base → Derived
析构顺序:Derived → Base(虚析构 否则析构不完全)
📏 9. 内存对齐与 sizeof 填充
struct A {
char c;
int i;
};
实际 sizeof(A)
是 8,不是 5,因为 int 要求对齐(通常 4 字节),结构体要按最大对齐值对齐。
可使用 #pragma pack(1)
控制对齐,但可能影响性能。
🧰 10. 自定义内存池(Memory Pool)
用于高性能场景(游戏、数据库、嵌入式):
- 减少频繁调用系统
malloc/free
- 提前分配大块内存
- 内部分页、块管理
适合:
- 大量小对象(如实体组件、消息包)
- 固定大小的对象分配(定长池)
🔍 11. 内存错误常见类型
类型 | 示例 |
---|---|
内存泄漏 | malloc 后未 free |
悬空指针 | delete 后仍使用指针 |
越界访问 | 数组越界 |
double free | 多次释放同一指针 |
use-after-free | 指针释放后再次使用 |
未初始化内存使用 | 未设置初值就使用 |
工具推荐:
- Valgrind:Linux 上常用
- ASAN(Address Sanitizer):现代 C++ 编译器支持
- Visual Studio Memory Leak Detection
🛡️ 12. RAII 与现代 C++ 内存安全
RAII(资源获取即初始化)核心思想:
构造时获取资源,析构时自动释放!
搭配智能指针使用:
std::unique_ptr<T>
std::shared_ptr<T>
std::weak_ptr<T>
无需手动 delete,资源自动回收。
📚 参考链接
需要我为这个主题做成视频脚本、教学PPT、工程实战代码,或者搭配内存图讲解都可以。是否继续深入如:虚函数表图解 + 内存池代码实现 + new/delete 重载实战?欢迎告诉我目标场景(面试、学习、项目优化)。
发表回复