携程 C++ 一面面经 总结
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 你做的东西日活 / 并发多少,出过什么故障?
- memcpy 和 memmove 的区别?什么时候会重叠?
- C++ 里野指针和悬垂指针最常见来源?
- new 一个对象的过程?placement new 用在哪?
- 多态你怎么用?什么场景不该用?
- 虚函数能被内联吗?什么情况下可以?
- vector 迭代器失效的根本原因?
- STL 的 allocator 模板参数干什么用的?
《参考解析》
开场的业务量级与故障题:这是 C++ 面试里最容易拉开差距的一问,考的是工程成熟度而不是语法。答法分三层:先把量级交代清楚(QPS、峰值、p99 延迟、数据量),数字要诚实——小系统就报小系统的数,硬吹大厂量级一追问细节就塌;再讲一次完整的线上故障故事,结构是「现象 → 影响面 → 止血 → 根因 → 防范」,止血讲你怎么快速恢复(回滚、限流、开关),防范讲你后来加了什么(监控、报警、校验、灰度);最后可以主动说一下你负责的那部分边界。
memcpy / memmove:memcpy 假定源和目的不重叠,按前向逐字节拷贝;当两块内存有交集时,前向拷贝会覆盖还没读完的源数据,结果取决于方向就是错的。memmove 会先判断方向(源在目的之前就从后往前拷),所以慢一点但安全。实践规则:环形缓冲的分段拷贝、同一 buffer 内搬移数据默认用 memmove;性能热点上确认不重叠再用 memcpy(编译器还能做向量化优化)。这类题常被追问「怎么判断重叠」——比较地址区间,dst < src + n && src < dst + n。
指针生命周期:野指针(未初始化或已 delete 未置空)和悬垂指针(指向已释放或已移走的内存)的高频来源是:返回局部变量的地址或引用、delete 之后仍然使用或没有置空、容器扩容后继续用旧迭代器 / 指针、对象析构后回调没有反注册、以及多线程里对象已被别的线程释放。难查的原因是后果延迟暴露——踩踏内存后往往在很久之后的下一次调用才崩,stack 上看不出关系。防法是 RAII + 智能指针(unique_ptr / shared_ptr / weak_ptr 打破循环引用)、容器操作后重新取迭代器、析构里反注册回调,CI 里挂 ASan / UBSan 兜底。
new 与 placement new:new T(args) 分两步——先调 operator new 分配原始内存,再在该地址上执行构造函数,最后返回指针;delete 反向执行析构再 operator delete 释放。placement new 是在已经拿到的内存上构造对象,常用于对象池复用、共享内存段中构造、以及容器自行管理缓冲区。它的义务是手动调用析构函数(p->~T())并且不能对这个指针调用 delete,构造与析构必须严格配对;异常安全上如果构造函数抛异常,要在包装层保证已分配的资源被释放。
多态的使用边界:多态适合接口稳定、实现多变、需要运行期组合的场景,比如策略模式、插件体系、设备抽象层、框架扩展点。不该用的场景是:每帧百万次调用的热循环(虚函数调用阻止内联、还有额外间接寻址)、内存极省的端侧、以及类型集合在编译期就固定已知(模板 / CRTP 更合适,且能内联)。判断标准很朴素:先问「这个抽象未来真的会扩展吗」,不会就是过度设计;热点路径要用 perf 数据验证后再决定是否去虚拟化。
虚函数内联与去虚拟化:常规情况下虚函数不能被内联,因为编译期不知道动态类型、拿不到目标函数地址。但在编译器能证明唯一目标时可以:对象是具体类型(Derived d; d.foo(); 这种值语义调用)、类是 final 或方法是 final、或者全程序优化(LTO)能推出唯一实现。所以 Derived d; d.foo(); 比 Base& b = d; b.foo(); 快,但不要为了这点性能牺牲接口清晰度。
vector 迭代器失效与 allocator:vector 是连续内存,扩容时会整体搬家,所有指向旧缓冲的指针、引用、迭代器全部失效——这是「全部失效」不是「部分失效」,也是最容易踩的地方;中间 insert / erase 会失效插入 / 删除点之后的迭代器,很多实现里 end() 也可能变。习惯写法是循环删除用 it = c.erase(it),需要跨调用持有元素就存下标或指针(配合 reserve 让地址稳定),或者改用 std::list / std::deque / 以索引为键的容器。allocator 模板参数的作用是把「怎么申请内存」从容器里抽出来:默认走 operator new,可以换成内存池、arena、带统计或对齐保证的自定义分配器。它是 STL 分层设计的典范——上层逻辑零改动就能切换分配策略,高频短生命周期对象的服务换池式 allocator 收益很明显,C++17 之后还可用 std::pmr 的多态内存资源。
这一轮的特点:题目都是 C++ 的「老题」,但每一道都能往工程深处引——memmove 引到环形缓冲、迭代器失效引到容器选型和引用稳定性、allocator 引到性能调优。这类面试评分点不在「知不知道」,而在「有没有被这些坑打过」。回答时带上你实际踩过的场景(比如某次扩容导致的崩溃、某次 ASan 抓出的 use-after-free),可信度会明显不同。