优必选 C++开发工程师 一面
1. 自我介绍
2. 你在“分布式对象存储网关”里具体实现了什么?
我负责三块:
- 接入层协议处理:请求解析、鉴权、限流与路由;
- 高并发内存管家:减少频繁 malloc/free,降低锁竞争;
- 日志改造:同步日志改为异步批量落盘 + 结构化字段。
产出结果:峰值吞吐提升,P99 延迟下降,日志检索效率提升,线上问题定位时间缩短。
3. 怎么解决 TCP 黏包/拆包问题?
TCP 是字节流,没有消息边界。常见解决方案:
- 固定长度协议;
- 分隔符协议;
- 长度字段 + 消息体(工程最常用)。
推荐“长度前缀”方案:先读头拿到长度,再按长度读取完整包,配合循环缓冲区处理半包。
代码(伪代码):
while (buffer.readableBytes() >= 4) {
uint32_t len = buffer.peekUint32();
if (buffer.readableBytes() < 4 + len) break; // 半包
buffer.retrieve(4);
auto msg = buffer.readBytes(len);
handle(msg);
}
4. 说一下高并发内存管家“三层结构”设计
典型三层:
- Thread Cache:线程本地小对象缓存,减少锁竞争;
- Central Cache/调度层:跨线程平衡内存块;
- Page Heap:管理大页,从 OS 申请/归还。
目标:小对象快速分配,大对象统一管理,整体降低系统调用频率与碎片率。
5. 你这个内存管家是课程作业还是自主工程?如何证明工程价值?
- 理论来源可参考经典分配器思想;
- 但工程化做了压测、观测、回收策略、OOM 保护。 证明价值看指标:QPS、P99、CPU 占用、内存碎片率、故障率变化。
6. 中间调度层(Central Cache)如何设计?哈希 key 用什么?
调度层通常按 size class 管理空闲链表。 哈希 key 不是“对象地址”,而是规格类(如 8B、16B、...)。 这样可以把同规格对象批量转移给线程缓存,减少碎片与管理复杂度。
7. 第三层 Page Heap 的哈希 key 是什么?
常见 key 设计为:
- 页数(span pages) 或
- 起始页号(page id)。 前者用于按大小快速分配,后者用于定位与合并相邻 span。 工程上通常两种索引并存:一个按大小找空闲块,一个按地址做合并。
8. 每一页大小一样吗?页大小如何确定?
在同一分配器内,基础页大小通常固定(如 4KB/8KB),上层通过多个连续页组成 span。 页大小选择受:TLB 命中、内核页配置、对象分布、内部碎片率影响。 若业务大对象多,可结合 huge page 策略,但要评估内存浪费与回收效率。
9. 页怎么合并?如何避免外部碎片恶化?
释放 span 时,根据页号查前后邻居是否空闲:若空闲则合并成更大 span,再挂回空闲结构。 关键是 O(1)/O(logN) 找邻居,避免线性扫描。
代码(伪代码):
Span* s = freeSpan; auto prev = byPageId.find(s->start - 1); auto next = byPageId.find(s->end + 1); if (prev && prev->free) s = merge(prev, s); if (next && next->free) s = merge(s, next); insertBySizeClass(s); insertByPageId(s);
10. 你怎么改造日志链路,保证高并发下可观测且低开销?
从“同步逐条写盘”改为“异步批量 + 结构化 + 分级采样”:
- 业务线程只做轻量入队;
- 后台线程批量刷盘/上报;
- 关键字段结构化(trace_id、tenant、error_code);
- 高峰时动态采样,保错误全量。 效果:显著降低主链路阻塞,同时提升排障效率。
11. STL 常用容器及特点
- vector:连续内存,随机访问快,尾插高效;
- deque:分段连续,头尾插入较稳;
- list:节点离散,插删快但 cache 不友好;
- unordered_map:均摊 O(1) 查找,注意哈希退化与扩容抖动;
- map:有序,O(logN),适合范围查询。 选型核心:访问模式、迭代稳定性、内存局部性。
12. vector 中间插入会发生什么?内存不够怎么办?
中间插入会导致插入点后元素整体搬移;若容量不足会触发扩容并整体搬迁,迭代器/引用可能失效。 优化:
- 预留容量 reserve;
- 高频中间插入场景改用 deque/list 或分块结构;
- 对大对象可存指针/句柄降低搬移成本。
13. 虚函数与虚表底层机制是什么?虚表是“类一份”还是“对象一份”?
有虚函数的类通常存在一张虚函数表(vtable),对象里有一个虚表指针(vptr)。 调用虚函数时通过 vptr 间接分派到实际函数实现,实现运行时多态。 一般是“每个动态类型一张虚表,每个对象一个 vptr”。 多重继承场景会更复杂(可能有多个 vptr 子对象布局)。
14. 为什么基类析构函数通常要设为 virtual?
若通过基类指针删除派生类对象,而基类析构非虚,会触发未定义行为(常见是派生资源未
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.