优必选 C++开发工程师 一面

1. 自我介绍

2. 你在“分布式对象存储网关”里具体实现了什么?

我负责三块:

  1. 接入层协议处理:请求解析、鉴权、限流与路由;
  2. 高并发内存管家:减少频繁 malloc/free,降低锁竞争;
  3. 日志改造:同步日志改为异步批量落盘 + 结构化字段。

产出结果:峰值吞吐提升,P99 延迟下降,日志检索效率提升,线上问题定位时间缩短。

3. 怎么解决 TCP 黏包/拆包问题?

TCP 是字节流,没有消息边界。常见解决方案:

  1. 固定长度协议;
  2. 分隔符协议;
  3. 长度字段 + 消息体(工程最常用)。

推荐“长度前缀”方案:先读头拿到长度,再按长度读取完整包,配合循环缓冲区处理半包。

代码(伪代码):

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. 说一下高并发内存管家“三层结构”设计

典型三层:

  1. Thread Cache:线程本地小对象缓存,减少锁竞争;
  2. Central Cache/调度层:跨线程平衡内存块;
  3. 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. 你怎么改造日志链路,保证高并发下可观测且低开销?

从“同步逐条写盘”改为“异步批量 + 结构化 + 分级采样”:

  1. 业务线程只做轻量入队;
  2. 后台线程批量刷盘/上报;
  3. 关键字段结构化(trace_id、tenant、error_code);
  4. 高峰时动态采样,保错误全量。 效果:显著降低主链路阻塞,同时提升排障效率。

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++ 常考面试题总结 文章被收录于专栏

本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

全部评论

相关推荐

03-04 07:14
门头沟学院 C++
何木健一:去啥?你能考虑去就是思想有问题,当然一周到岗一天可以考虑一下😨
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务