极氪-C++嵌入式开发方向-一面
1. C++中智能指针有哪些?它们的区别和使用场景是什么?
答案:
1.智能指针类型 C++11引入了三种智能指针:unique_ptr、shared_ptr和weak_ptr。它们都定义在头文件中,用于自动管理动态分配的内存,避免内存泄漏和悬空指针问题。
2.unique_ptr独占所有权 unique_ptr实现独占式拥有,同一时刻只能有一个unique_ptr指向某个对象。当unique_ptr被销毁时,它所指向的对象也会被自动删除。unique_ptr不能被复制,只能通过std::move转移所有权。适用场景是明确对象只有一个拥有者的情况,比如工厂模式返回的对象、RAII资源管理等。
3.shared_ptr共享所有权 shared_ptr实现共享式拥有,允许多个shared_ptr指向同一个对象,内部使用引用计数机制。当最后一个shared_ptr被销毁时,对象才会被删除。shared_ptr可以被复制和赋值,每次复制引用计数加1,销毁时减1。适用于多个对象需要共享同一资源的场景,比如观察者模式、缓存系统等。
4.weak_ptr解决循环引用 weak_ptr是shared_ptr的辅助指针,它指向shared_ptr管理的对象但不增加引用计数。weak_ptr主要用于解决shared_ptr的循环引用问题,比如父子节点互相持有对方的shared_ptr会导致内存泄漏。使用weak_ptr时需要通过lock()方法获取shared_ptr才能访问对象,如果对象已被销毁则返回空指针。
5.性能和使用建议 unique_ptr没有引用计数开销,性能最好,应该作为首选。shared_ptr有引用计数的原子操作开销,在多线程环境下性能较差。在车载系统中,我通常用unique_ptr管理传感器对象,用shared_ptr管理配置数据,用weak_ptr处理回调函数避免循环引用。
2. 请介绍一下你做过的项目,重点说说技术难点
参考答案:
1.项目背景 我最近做的是一个车载娱乐系统的中间件开发项目,基于Linux平台使用C++实现。系统需要处理多媒体播放、导航、语音交互等多个模块的协调工作,对实时性和稳定性要求很高。
2.技术架构 我负责的是服务层的开发,采用了分层架构设计。底层是硬件抽象层HAL,中间是业务逻辑层,上层是应用接口层。模块间通信使用了进程间通信IPC机制,主要是共享内存和消息队列。为了提高性能,我使用了线程池处理并发请求,用智能指针管理资源避免内存泄漏。
3.核心技术难点 最大的难点是多线程并发控制和性能优化。系统有多个模块同时运行,需要访问共享资源如配置文件、音频设备等。我使用了读写锁和条件变量实现线程同步,用无锁队列优化了消息传递性能。另外,为了降低延迟,我实现了对象池复用频繁创建的对象,使用内存池减少内存分配开销。
4.遇到的问题和解决 开发过程中遇到过内存泄漏问题,通过Valgrind工具定位到是某个回调函数中的循环引用导致的。我将shared_ptr改为weak_ptr解决了这个问题。还遇到过死锁问题,通过统一锁的获取顺序和使用RAII机制自动释放锁来避免。性能方面,通过perf工具分析发现频繁的字符串拷贝是瓶颈,改用string_view和移动语义后性能提升了30%。
3. 你在项目中使用过哪些设计模式?举例说明
答案:
1.单例模式 在车载系统中,配置管理器、日志管理器等全局唯一的对象我使用了单例模式。采用了懒汉式单例,使用局部静态变量保证线程安全,C++11保证了静态局部变量初始化的线程安全性。这样避免了全局变量的污染,也保证了对象的唯一性。
2.工厂模式 对于不同类型的传感器对象创建,我使用了工厂模式。定义了一个传感器工厂类,根据配置文件中的类型参数创建对应的传感器对象。这样新增传感器类型时只需要添加新的子类和工厂方法,不需要修改现有代码,符合开闭原则。工厂方法返回unique_ptr确保资源管理的安全性。
3.观察者模式 在事件通知机制中使用了观察者模式。比如传感器数据更新时需要通知多个订阅者,我定义了Subject和Observer接口,传感器作为Subject,各个业务模块作为Observer。使用weak_ptr存储观察者列表避免循环引用,通知时先lock()检查对象是否有效。
4.策略模式 在负载均衡实现中使用了策略模式。定义了LoadBalanceStrategy接口,实现了轮询、随机、加权等多种策略。客户端可以动态切换策略而不需要修改代码。这种设计使得系统更加灵活,易于扩展新的负载均衡算法。
5.RAII模式 虽然不是传统的设计模式,但RAII在C++中非常重要。我在资源管理中大量使用了RAII,比如用智能指针管理内存,用lock_guard管理互斥锁,用unique_ptr管理文件句柄。这样确保了资源在任何情况下都能正确释放,即使发生异常也不会泄漏。
4. STL容器中vector和list的区别?什么时候用vector什么时候用list?
答案:
1.底层实现差异 vector是动态数组实现,内存连续存储,支持随机访问,通过下标访问元素的时间复杂度是O(1)。list是双向链表实现,内存不连续,不支持随机访问,访问元素需要遍历,时间复杂度是O(n)。vector在内存中是一块连续空间,cache友好性好,而list的节点分散在内存中,cache命中率低。
2.插入删除性能 vector在尾部插入删除是O(1),但在中间插入删除需要移动元素,时间复杂度是O(n)。当容量不足时需要重新分配内存并拷贝所有元素,开销较大。list在任意位置插入删除都是O(1),只需要修改指针,不需要移动元素。但list的每个节点都有额外的指针开销,内存占用比vector大。
3.内存管理特点 vector预分配内存,capacity通常大于size,有一定的内存浪费但减少了重新分配次数。可以通过reserve()预留空间,通过shrink_to_fit()释放多余空间。list按需分配,每次插入都要分配新节点,频繁分配释放可能导致内存碎片。
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。
