蓝芯机器人 C++开发 一面
1、讲讲 C++ 动态内存分配
答案:C++ 里的动态内存分配主要指在程序运行过程中按需从自由存储区申请内存,而不是在编译期就把对象大小和生命周期完全定死。最常见的方式就是 new/delete 和 new[]/delete[],更工程化一点就是通过智能指针、容器和分配器来管理。
从过程上看,动态内存分配通常分成两层:一层是“拿到原始内存”,另一层是“在这块内存上构造对象”。new 会先调用底层的 operator new 分配足够大小的原始内存,再调用构造函数初始化对象;delete 则反过来,先调用析构函数,再调用 operator delete 释放原始内存。
和 C 里的 malloc/free 相比,C++ 动态内存分配更强调对象语义,而不只是字节块。malloc 只管给一块内存,不会构造对象;new 不只是分配,还会完成初始化。
实际开发里,如果直接手写 new/delete 很多,就容易出现泄漏、悬空指针、重复释放,所以现代 C++ 更推荐容器和智能指针来接管大部分动态内存场景。
代码:
#include <iostream>
using namespace std;
class A {
public:
A() { cout << "A construct\n"; }
~A() { cout << "A destruct\n"; }
};
int main() {
A* p = new A; // 分配内存 + 调用构造
delete p; // 调用析构 + 释放内存
return 0;
}
2、new 的底层
答案:new 本质上不是单纯的库函数,而是一个运算符。它底层通常做两件事:先通过 operator new 申请原始内存,再在那块内存上调用构造函数。
比如这句:
A* p = new A();
大致可以理解成下面这样的过程:
void* mem = operator new(sizeof(A)); A* p = new(mem) A();
也就是说,operator new 负责分配内存,真正的对象构造是在这块内存上完成的。如果构造函数抛异常,编译器还会自动调用 operator delete 把刚才那块内存释放掉,防止泄漏。
继续往下说,operator new 默认实现一般会向堆申请内存,申请失败时默认抛出 std::bad_alloc。如果用户自己重载了类内 operator new,那这个类的对象分配策略也可以被定制,比如对象池、小块内存池、对齐分配。
代码:
#include <iostream>
#include <new>
using namespace std;
class A {
public:
A() { cout << "construct\n"; }
~A() { cout << "destruct\n"; }
void* operator new(size_t sz) {
cout << "custom operator new, size = " << sz << endl;
return ::operator new(sz);
}
void operator delete(void* ptr) {
cout << "custom operator delete\n";
::operator delete(ptr);
}
};
int main() {
A* p = new A;
delete p;
}
3、介绍一下智能指针,然后详细讲一个最常用的
答案:智能指针本质上是用 RAII 思想封装资源管理,把“对象什么时候释放”这件事交给对象生命周期,而不是交给人工记忆。常见的有三种:
unique_ptr:独占所有权,不能拷贝,只能移动shared_ptr:共享所有权,靠引用计数管理对象生命周期weak_ptr:弱引用,不拥有对象,主要用来观察shared_ptr管理的对象并打破循环引用
如果详细讲最常用的,我会讲 shared_ptr。因为它既常用,也最容易被问深。
shared_ptr 的核心不只是“能共享”,而是它背后有一个控制块。这个控制块里通常会存:
- 强引用计数
- 弱引用计数
- 删除器
- 分配器信息
- 被管理对象地址或者对象构造信息
每拷贝一次 shared_ptr,强引用计数加一;每析构一个 shared_ptr,强引用计数减一;当强引用计数变成 0,对象析构;当弱引用计数也归零时,控制块本身才释放。
它的好处是多个模块之间可以共享同一个对象,不用人工协调释放时机。但问题也很明显:
- 有额外计数开销
- 有控制块内存开销
- 容易循环引用
- 以为“有
shared_ptr就线程安全”其实是误解
代码:
#include <iostream>
#include <memory>
using namespace std;
class A {
public:
A() { cout << "A construct\n"; }
~A() { cout << "A destruct\n"; }
};
int main() {
shared_ptr<A> p1 = make_shared<A>();
cout << p1.use_count() << endl;
{
shared_ptr<A> p2 = p1;
cout << p1.use_count() << endl;
}
cout << p1.use_count() << endl;
return 0;
}
4、weak_ptr、shared_ptr 和普通 new 出来的空间有什么区别,make_shared 做了什么
答案:普通 new 出来的空间,本质上只是你手里拿到一个裸指针,生命周期完全靠你自己管理。它没有引用计数、没有控制块、没有自动释放,也没有弱引用概念。
shared_ptr 则不同。它除了指向对象本身,还会关联一个控制块,控制块记录引用计数等元信息。也就是说,shared_ptr 管理的不是“一个裸地址”这么简单,而是“对象 + 一套所有权管理机制”。
weak_ptr 不拥有对象,它不增加强引用计数,只是指向同一个控制块。所以它和 shared_ptr 的关系更像是“我知道这个对象可能存在,但我不负责让它活着”。访问时要先通过 lock() 尝试提升成 shared_ptr,如果对象已经销毁,lock() 会得到空指针。
make_shared 的关键点在于它通常会做一次更高效的联合分配,把“控制块”和“对象本体”尽量放在一块连续内存里,这样比 shared_ptr<T>(new T) 少一次独立分配,缓存局部性也更好。而 shared_ptr<T>(new T) 常见情况是:
new T分配对象shared_ptr再单独
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

