3. C++ 内存管理核心:内存分配、智能指针、RAII、内存池
new/delete 底层机制、智能指针线程安全、RAII 资源封装、内存池设计展开。掌握回答框架可避免“只会用不会说”的困境。
核心回答框架图
面试问答卡片
问题 1:请比较 malloc 和 new 的区别,在性能上哪个更优?
考察点:基础内存分配机制、构造函数调用、底层实现差异、性能意识
回答框架:1) 语法区别(函数 vs 运算符) 2) 是否调用构造函数 3) 失败处理(返回 NULL vs 抛异常) 4) 底层实现(new 内部调用 malloc) 5) 性能对比
示例回答:
new 是运算符,会调用构造函数;malloc 是函数,只分配裸内存。new 内部通常调用 malloc,因此分配阶段性能接近,但 new 多了构造开销。在大量对象创建时,可使用 placement new 配合内存池减少构造次数。
可能追问:如何重载 operator new 实现自定义分配?calloc 与 malloc 的区别?
delete 和 free 的析构语义。
问题 2:你知道哪些智能指针?什么时候优先用 unique_ptr 而不是 shared_ptr?
考察点:智能指针选型、所有权设计、性能开销理解
回答框架:1) 列举三种智能指针(unique_ptr、shared_ptr、weak_ptr) 2) 所有权语义:独占 vs 共享 3) 性能开销:shared_ptr 有控制块和原子计数 4) 场景举例:局部持有、工厂返回用 unique_ptr;多个所有者用 shared_ptr
示例回答:
通常优先 unique_ptr,因为它零开销抽象,编译期确定所有权,不支持拷贝,移动性能极佳。只有在需要多个对象共享同一资源时才使用 shared_ptr。若存在循环引用,需配合 weak_ptr 打破。
可能追问:make_shared 和直接 new shared_ptr 有什么性能差异?控制块存在哪里?
unique_ptr 可转换为 shared_ptr,但反之不行。
问题 3:请解释 RAII 在 C++ 中的核心价值,并举一个非资源管理的例子。
考察点:RAII 本质理解、异常安全、作用域绑定
回答框架:1) 定义:资源获取即初始化,析构函数自动释放 2) 三大价值:异常安全、代码简洁、防止泄漏 3) 非资源例子:锁守卫、时间计时器、Callback 注册/注销
示例回答:
RAII 核心是将资源生命周期绑定到对象作用域。例如 std::lock_guard 在构造时加锁,析构自动解锁,即使中途抛出异常也能释放锁。另一种实践:测量耗时——构造记录时间点,析构打印耗时,保证代码块执行完毕自动输出。
可能追问:RAII 与 GC 对比?在移动语义下 RAII 如何处理资源转移?
项目案例 · 内存池实战
项目名称:高性能游戏服务器内存池优化
| 业务背景 | 大型多人在线游戏,每个玩家每秒发送位置、技能等消息,服务器需频繁创建/销毁短生命周期的小对象(如协议包、技能效果对象)。系统 malloc 耗时高且碎片严重,触发 OOM 风险。 |
|---|---|
| 规模指标 | 在线玩家 5 万+,QPS 约 20 万,对象创建频率 12 万次/秒,平均对象大小 64~128 字节。 |
| 技术栈 | C++17、Boost、自定义 Allocator、TCMalloc 部分参考、线程局部缓存。 |
| 核心难点 | 1) 多线程无锁分配;2) 页面外碎片回收;3) 与 STL 容器适配。 |
| 面试高频追问 |
|
| 可迁移回答话术 | “我当时主导了内存池模块,核心思路是定长内存池 + 线程局部缓存。通过预分配大块虚拟内存,切分固定大小 chunk,用 FreeList 回收。改造后 new 操作减少 90%,应用层 P99 延迟从 20ms 降到 8ms。类似思路可迁移到网络包缓冲区、日志对象池等场景。” |