猛犸 C++软件开发 二面 面经
1. 项目中遇到过内存泄漏吗?是怎么定位和解决的?
定位手段:
- 使用 Valgrind 的 memcheck 工具运行程序,输出泄漏点的完整调用栈
- 使用 AddressSanitizer(ASan)编译时加 -fsanitize=address,运行时检测,开销比 Valgrind 小
- 嵌入式环境没有工具时,可以在关键路径打印内存使用量,观察是否持续增长
常见原因:
- new 之后异常路径跳过了 delete
- 容器存放裸指针,容器清空时只释放了容器本身
- shared_ptr 循环引用导致引用计数无法归零
- 回调或闭包中持有对象导致生命周期延长
解决方向:推行 RAII,用智能指针替代裸指针,从根本上消除手动管理的风险。
2. 说说 epoll 的 ET 模式和 LT 模式的区别,ET 模式下编程需要注意什么?
- LT(水平触发):只要缓冲区中还有数据,每次 epoll_wait 都会通知,没读完下次还会触发,编程简单不易出错
- ET(边缘触发):只在状态发生变化时通知一次,比如从无数据变为有数据,如果没读完这次数据,下次不会再通知
ET 模式注意事项:
- fd 必须设置为非阻塞,否则最后一次 read 会永久阻塞
- 每次触发后必须循环读取直到返回 EAGAIN,确保数据读完
- 写事件同理,写完后要及时取消对 EPOLLOUT 的监听,否则会一直触发
- ET 模式减少了 epoll_wait 的唤醒次数,在高并发场景下性能更好,但编程复杂度更高
3. 说说你对设计模式的理解,项目中用过哪些?
设计模式是针对常见软件设计问题的可复用解决方案,分三类:
- 创建型:关注对象的创建方式,如单例、工厂、建造者
- 结构型:关注类和对象的组合方式,如适配器、代理、装饰器
- 行为型:关注对象间的通信方式,如观察者、策略、命令
项目中常用的:
- 单例模式:全局唯一的配置管理器、日志模块
- 观察者模式:相机帧数据到来时通知多个处理模块,解耦数据源和消费者
- 策略模式:图像处理算法可替换,不同场景切换不同策略,避免大量 if-else
- 工厂模式:根据配置动态创建不同类型的编解码器或协议处理器
4. 说说 C++ 中对象的内存布局,含虚函数和继承时有什么变化?
普通对象:
- 按成员声明顺序排列,遵循内存对齐规则,编译器可能插入填充字节
含虚函数:
- 对象起始位置插入一个虚指针(vptr),指向该类的虚函数表(vtable)
- vtable 存放该类所有虚函数的函数指针,是只读的静态数据
单继承:
- 子类对象内存中先放父类部分(包含父类 vptr),再放子类自己的成员
- 子类重写虚函数后,vtable 中对应槽位替换为子类的函数指针
多继承:
- 子类对象中包含多个父类子对象,每个父类子对象有自己的 vptr
- 存在地址偏移问题,指针转换时编译器会自动调整偏移量
- 虚继承用于解决菱形继承的二义性,引入虚基类指针,布局更复杂
5. 说说线程池在实际项目中如何做优雅关闭?
优雅关闭的目标是让线程处理完当前任务后再退出,而不是强制终止。
实现步骤:
- 设置一个原子停止标志 stop,调用 shutdown 时将其置为 true
- 停止后不再接受新任务,但任务队列中已有的任务继续执行完
- 通过条件变量广播唤醒所有阻塞等待的线程,让它们检查停止标志后退出
- 主线程依次 join 所有工作线程,确保所有任务执行完毕再返回
需要注意的点:
- 析构函数中要做 join,防止线程还在运行时对象已销毁
- 任务队列的锁在 shutdown 时要正确释放,避免死锁
- 如果需要立即停止,可以增加一个 force_stop 标志,线程检查到后丢弃剩余任务直接退出
6. 说说 TCP 粘包和拆包问题是怎么产生的,如何解决?
产生原因:
- TCP 是字节流协议,没有消息边界的概念,发送方的多次 write 可能被合并成一个包发送(粘
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
C++八股文全集 文章被收录于专栏
本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。