顺丰C++开发 二面 面经
1. 介绍一下你最有挑战性的项目,重点说说技术架构和你解决的核心问题
回答框架:
- 项目背景和规模(用户量、数据量、QPS)
- 整体架构设计(画图说明)
- 遇到的技术难点(性能、并发、一致性)
- 解决方案和优化措施
- 最终效果(性能提升、成本降低)
2. 线上服务突然CPU飙升到100%,你会如何快速定位问题
答案:
定位步骤:
top命令找到占用高的进程PIDtop -H -p <PID>查看线程级CPU占用,找到问题线程TID- 将TID转为16进制:
printf "%x\n" <TID> - 查看线程堆栈: C++程序:pstack <PID> 或 gdb attach <PID>然后thread apply all bt或用perf top -p <PID>查看热点函数
strace -p <PID> -c统计系统调用,看是否频繁调用某个系统调用perf record -p <PID> -g记录性能数据,生成火焰图分析
常见原因:
- 死循环或忙等待
- 锁竞争导致自旋
- 频繁的系统调用
- 正则表达式回溯
- 算法复杂度过高
3. 说说C++的内存管理,如何排查内存泄漏
答案:
内存泄漏检测工具:
- Valgrind:valgrind --leak-check=full --show-leak-kinds=all ./app优点:详细的泄漏报告和调用栈缺点:性能损耗大(10-50倍),只能测试环境用
- AddressSanitizer:编译时加-fsanitize=address -g优点:性能损耗小(2倍),能检测更多问题(use-after-free、buffer overflow)缺点:需要重新编译
- tcmalloc/jemalloc:替换默认allocator,提供内存分析可以在生产环境使用提供heap profiling功能
常见泄漏原因:
- new/delete不匹配(new[]要配delete[])
- 异常导致未释放(没用RAII)
- 容器存裸指针,容器销毁时不会delete
- shared_ptr循环引用
- 第三方库泄漏
预防措施:
- 优先用智能指针(unique_ptr/shared_ptr)
- 用RAII管理资源
- 容器存对象或智能指针,不存裸指针
- 定期压测和内存监控
4. 讲讲epoll的工作原理,ET和LT模式有什么区别
答案:
epoll优势:
- 没有fd数量限制(select最多1024)
- 不需要遍历所有fd,只返回就绪的fd
- 使用mmap共享内存,减少内核和用户空间的数据拷贝
LT模式(水平触发):
- 只要fd就绪就会一直通知
- 未处理完的数据下次epoll_wait还会通知
- 编程简单,不容易丢事件
- 类似select/poll的行为
ET模式(边缘触发):
- 只在状态变化时通知一次
- 必须一次性读完所有数据(循环read直到EAGAIN)
- 性能更高,减少epoll_wait调用次数
- 编程复杂,容易丢事件
ET模式使用要点:
- 必须配合非阻塞IO
- 读写都要循环处理直到EAGAIN
- 注意EPOLLONESHOT,避免多线程竞争同一个fd
示例场景:
- 高性能服务器优先用ET模式
- 业务逻辑复杂的用LT模式,降低开发难度
5. MySQL的索引原理,什么情况下索引会失效
答案:
索引类型:
- B+树索引(InnoDB默认):叶子节点存数据,非叶子节点存索引
- 哈希索引(Memory引擎):等值查询快,不支持范围查询
- 全文索引:用于文本搜索
索引失效场景:
- 使用函数或表达式:
WHERE YEAR(date) = 2024 - 隐式类型转换:字符串字段用数字查询
WHERE phone = 12345678901 - 前缀模糊查询:
LIKE '%abc'(后缀模糊可以用索引) - OR条件中有未建索引的列
- 联合索引不满足最左前缀:索引(a,b,c),查询WHERE b=1不走索引
- NOT、!=、<>操作符
- IS NULL / IS NOT NULL(取决于数据分布)
- 优化器判断全表扫描更快:数据量小或选择性差
优化建议:
- 避免在索引列上使用函数
- 使用覆盖索引,避免回表
- 联合索引遵循最左前缀原则
- 用EXPLAIN分析执行计划
6. 说说MySQL的事务隔离级别,分别解决什么问题
答案:
四种隔离级别:
- READ UNCOMMITTED(读未提交):可以读到其他事务未提交的数据问题:脏读、不可重复读、幻读几乎不用
- READ COMMITTED(读已提交):只能读到已提交的数据解决:脏读问题:不可重复读、幻读Oracle默认级别
- REPEATABLE READ(可重复读):同一事务内多次读取结果一致解决:脏读、不可重复读问题:幻读(InnoDB通过MVCC和间隙锁解决)MySQL默认级别
- SERIALIZABLE(串行化):完全串行执行,最高隔离级别解决:所有并发问题问题:性能最差
InnoDB的MVCC:
- 通过版本链和ReadView实现
- 每行记录有隐藏字段:事务ID、回滚指针
- 读不加锁,写加锁
- 解决了大部分幻读问题
实际应用:
- 大部分场景用REPEATABLE READ
- 对一致性要求极高的用SERIALIZABLE
- 性能优先可以用READ COMMITTED
7. 如何设计一个高并发的秒杀系统
答案:
核心挑战:
- 瞬时高并发(10万QPS+)
- 超卖问题
- 黄牛刷单
架构设计:
客户端 → CDN(静态资源) → Nginx(限流) → 业务服务
↓
Redis(库存预减) → 消息队列 → 异步下单
↓
MySQL(订单)
关键技术:
- 前端优化:按钮置灰,防止重复点击验证码,增加刷单成本静态资源CDN加速
- 接入层限流:Nginx限流:limit_req_zone用户维度限流:1秒只能请求1次IP限流:防止单IP刷接口
- Redis预减库存:用DECR原子操作减库存库存为0直接返回,不打到数据库设置库存比实际多一点,防止超卖
- 消息队列削峰:请求进入Kafka/RabbitMQ消费者异步处理订单返回"排队中"给用户
- 数据库防超卖:乐观锁:UPDATE ... WHERE stock > 0 AND version = ?或悲观锁:SELECT ... FOR UPDATE库存分段:1000件拆成10个100件,减少锁竞争
- 防刷策略:风控系统识别异常行为黑名单机制人机验证
8. 说说C++的智能指针,shared_ptr的线程安全性
答案:
三种智能指针:
- unique_ptr:独占所有权,不能拷贝只能移动零开销,和裸指针性能一样适合明确所有权的场景
- shared_ptr:共享所有权,引用计数管理控制块包含:引用计数、弱引用计数、删除器引用计数用atomic保证线程安全
- weak_ptr:不增加引用计数用于打破循环引用使用前要lock()转成shared_ptr
shared_ptr线程安全性:
- 引用计数的增减是线程安全的(atomic操作)
- 对象本身的读写不是线程安全的,需要外部加锁
- 指针本身的修改不是线程安全的,多线程同时赋值需要加锁
举例说明:
shared_ptr<int> p = make_shared<int>(42); // 线程安全:拷贝shared_ptr shared_ptr<int> p2 = p; // 引用计数原子增加 // 不安全:修改对象 *p = 100; // 多线程需要加锁 // 不安全:修改指针本身 p = make_shared<int>(200); // 多线程需要加锁
循环引用问题:
- A持有B的shared_ptr,B持有A的shared_ptr
- 导致引用计数永不为0,内存泄漏
- 解决:其中一方用weak_ptr
9. 讲讲C++的虚函数实现机制,虚析构函数为什么重要
答案:
虚函数表(vtable):
- 每个有虚函数的类有一个vtable,存储虚函数地址
- 对象内存布局:[vptr][成员变量]
- vptr在构造函数中初始化,指向对应类的vtable
虚函数调用过程:
- 通过对象的vptr找到vtable
- 根据函数索引找到函数地址
- 间接调用该函数
多继承情况:
- 多个基类有虚函数时,对象有多个vptr
- 内存布局更复杂,可能需要指针调整
虚析构函数:
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
C++八股文全集 文章被收录于专栏
本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。
查看10道真题和解析