小米 C++开发 一面:智能指针、锁的选择与跨文件引用
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 自我介绍。
- 你对 C++ 和 C 的了解是怎样的?
- 智能指针了解吗?
- 在实习和项目中,你有哪些使用 AI 的经验?日常使用和自己做实验都可以聊。
- 再具体说一说,你对 C++ 和 C 语言的理解是什么?
- 进程和线程的区别是什么?
- 有哪些锁?分别的应用场景是什么?
- 操作系统内存管理知道什么?
- C++ 的内存管理知道什么?
- C 语言的内存泄漏是什么意思?
- C 语言如果是跨文件的引用,需要使用什么关键字?其他文件的函数怎么用?
- 价值观问题:和同事或者同学遇到分歧怎么解决?
- 自己认为最自豪的一件事情是什么?
- 手撕(三选一):判断由其他词构成的最长单词,返回它最长的长度。
《参考解析》
C 与 C++ 的差别要讲「抽象能力与代价」,不是背语法清单。 C 是过程式语言,手动管理内存,靠头文件与链接约定组合;C++ 在此基础上提供类与封装、模板与泛型、RAII、异常、运算符重载、命名空间和 STL。编译器做更多优化,但也引入对象布局、异常表、模板实例化膨胀等成本。工程上要讲清「零开销抽象」的前提:虚函数有 vtable 间接调用,异常有栈展开成本,按值传递会拷贝,所以接口多用 const 引用、配合移动语义。C++ 兼容大部分 C 语法,但真正的差别是资源生命周期交给类型系统管理,而不是「带类的 C」。
智能指针解决的是所有权表达。 unique_ptr 独占、不可拷贝可移动、默认没有额外开销,应该作为首选;shared_ptr 用控制块里的强引用与弱引用计数共享所有权,拷贝有原子计数开销,make_shared 会把对象和控制块一次分配,代价是 weak_ptr 存活时对象内存不释放;weak_ptr 不增加计数,用来打破循环引用(双向链表、父子节点、观察者)。线程安全边界要说清:引用计数的增减是原子的,但被指向对象的读写不因此安全,仍需要额外同步。裸 new 和 delete 只应出现在实现底层容器、或者与 C 接口交互的地方,业务代码里出现基本就是设计问题。
进程与线程:地址空间、开销、通信与故障域。 进程有独立的虚拟地址空间,切换要换页表、开销大、隔离好,通信靠管道、消息队列、共享内存、socket;线程共享地址空间与文件描述符,切换只换寄存器和栈,通信直接读内存但必须同步,一个线程崩溃可能拖垮整个进程。C++ 里 std::thread、std::mutex、std::condition_variable、std::atomic、future 与 promise 是基本件。判断该多进程还是多线程,看是需要强隔离(浏览器渲染进程、插件沙箱),还是高频共享数据的计算流水线——后者用线程,前者用进程。
锁的选择按「临界区长短加能否睡眠」来定。 互斥锁(std::mutex)在争用时让线程睡眠,适合临界区长、可能阻塞(IO、分配内存)的场景;自旋锁用 atomic_flag 忙等不睡眠,适合临界区只有几条指令、且运行在多核上,单核上自旋纯属浪费。中断上下文、内核软中断,以及持有自旋锁期间都不能睡眠,所以只能用自旋锁——这也是「中断里不能用互斥锁」的原因。用户态还要考虑读写锁(读多写少,但有写者饥饿与缓存行争用)、递归锁(通常是重入逻辑没理清)、条件变量配合互斥锁做等待通知、以及无锁结构(CAS 循环,ABA 问题用版本号解决)。粒度上宁可细粒度加明确加锁顺序,也不要一把大锁;防死锁就固定顺序,或用 std::scoped_lock 一次锁多个。
内存管理与内存泄漏:从虚拟内存讲到工具。 操作系统层面是虚拟地址到物理页的映射、页表与 TLB、缺页中断、伙伴系统与 slab 分配器;C++ 层面是栈(自动生命周期)、堆(new 与 malloc)、静态区和线程局部存储。new 表达式等于分配加构造,delete 等于析构加释放,数组必须 new[] 与 delete[] 配对。C 的内存泄漏指 malloc 的内存没有 free,程序长期运行后堆持续增长直至 OOM;C++ 更常见的泄漏是异常路径提前 return、容器里存裸指针未释放、shared_ptr 循环引用、以及只 release 不 delete 的 API 误用。定位手段有 ASan 与 LSan 直接报泄漏栈、valgrind —leak-check、自己维护分配计数,但最根本的还是 RAII——让每个资源都有明确属主。
跨文件引用与手撕题。 C 里跨文件使用别的文件的函数或变量,要在头文件里用 extern 声明:函数声明默认外部链接,变量必须写 extern,定义只放一个 .c 文件,配合头文件保护或 #pragma once;不想暴露给外部就用 static 把链接性收进本文件。C++ 在同样机制之上还有 namespace、C++17 的 inline 变量(解决头文件里定义多份的问题)和匿名 namespace。手撕「由其他词构成的最长单词」的常规解法是:按长度降序排序,用哈希集合做记忆化 DFS 或 DP,判断每个词能否由字典里更短的词拼出(自身不参与拼接),复杂度约为 O(n·L²);也可以建 Trie 后逐词 DFS。注意空串、重复词,以及必须由至少两个词组成的限制。