Momenta C++ 智驾 二面 面经

1. 自我介绍,介绍一下你觉得最有挑战性的项目

2. 你提到用过多任务架构,任务数量多了之后内存压力怎么处理的?静态分配和动态分配你怎么选择?

答:任务多了之后内存压力主要来自两块:每个任务的栈空间,以及任务间通信用的队列和信号量。

栈空间的处理:

  • 先用 uxTaskGetStackHighWaterMark 跑一段时间,看每个任务实际用了多少栈,按实际用量加一定余量分配,不要每个任务都给一个很大的默认值
  • 对于简单任务(只做状态机跳转、没有深层函数调用)可以给很小的栈,比如 128 字
  • 对于有 printf、sprintf 或者复杂字符串处理的任务,栈需要大一些

静态分配 vs 动态分配:

  • 嵌入式项目里我倾向于全部用静态分配,xTaskCreateStaticxSemaphoreCreateMutexStatic 这些接口
  • 原因是动态分配在运行时可能失败,而且堆碎片化问题在长时间运行的设备上会越来越严重
  • 静态分配在编译期就能知道内存布局,链接器会报错如果超出 RAM 限制,比运行时崩溃好排查得多
  • 动态分配适合原型阶段快速开发,量产前最好改成静态

3. 讲一下 C++ 的内存模型,std::atomic 的几种内存序分别是什么含义?

答:C++ 内存模型定义了多线程程序中内存操作的可见性和顺序规则。

六种内存序:

memory_order_relaxed

  • 只保证原子性,不保证顺序
  • 不阻止编译器和 CPU 重排
  • 适合只需要原子性的计数器,比如统计次数,不关心和其他操作的顺序关系

memory_order_acquire

  • 用于读操作,保证该操作之后的所有读写不会被重排到该操作之前
  • 配合 release 使用,保证看到 release 写入的值之后,也能看到 release 之前的所有写入

memory_order_release

  • 用于写操作,保证该操作之前的所有读写不会被重排到该操作之后
  • 典型用法:写完数据后 release 写一个标志,另一个线程 acquire 读到标志后能看到完整数据

memory_order_acq_rel

  • 同时具有 acquire 和 release 语义,用于读改写操作(fetch_add 等)

memory_order_seq_cst

  • 最强,所有线程看到的操作顺序完全一致
  • 默认值,性能开销最大,在 ARM 等弱内存序架构上会插入内存屏障指令

memory_order_consume

  • 比 acquire 弱,只保证依赖链上的顺序,实践中编译器通常把它当 acquire 处理,基本不用

实际使用:

std::atomic<bool> ready{false};
std::string data;

// 生产者
data = "hello";
ready.store(true, std::memory_order_release); // 保证 data 写入在 ready 之前

// 消费者
while (!ready.load(std::memory_order_acquire)); // 看到 true 后能看到完整的 data
std::cout << data;

4. 手写题:实现一个无锁的单生产者单消费者队列

答:

#include <atomic>
#include <vector>
#include <optional>

template<typename T>
class SPSCQueue {
public:
    explicit SPSCQueue(size_t capacity)
        : capacity_(capacity + 1), // 多一个槽位区分满和空
          buffer_(capacity + 1),
          head_(0),
          tail_(0) {}

    // 生产者调用,队列满返回 false
    bool push(T value) {
        size_t tail = tail_.load(std::memory_order_relaxed);
        size_t next = (tail + 1) % capacity_;

        // 队列满:下一个位置是 head
        if (next == head_.load(std::memory_order_acquire)) {
            return false;
        }

        buffer_[tail] = std::move(value);
        tail_.store(next, std::memory_order_release);
        return true;
    }

    // 消费者调用,队列空返回空
    std::optional<T> pop() {
        size_t head = head_.load(std::memory_order_relaxed);

        // 队列空:head == tail
        if (head == tail_.load(std::memory_order_acquire)) {
            return std::nullopt;
        }

        T value = std::move(buffer_[head]);
        head_.store((head + 1) % capacity_, std::memory_order_release);
        return value;
    }

    bool empty() const {
        return head_.load(std::memory_order_acquire) ==
               tail_.load(std::memory_order_acquire);
    }

private:
    const size_t capacity_;
    std::vector<T> buffer_;
    // 两个指针分别由生产者和消费者独占写,避免伪共享
    alignas(64) std::atomic<size_t> head_; // 消费者写
    alignas(64) std::atomic<size_t> tail_; // 生产者写
};

关键点:

  • head_ 只有消费者写,tail_ 只有生产者写,不需要锁
  • alignas(64) 让两个原子变量在不同缓存行,避免伪共享(false sharing)导致性能下降
  • push 里读 head 用 acquire,写 tail 用 release;pop 里读 tail 用 acquire,写 head 用 release
  • 容量加一是为了用空槽位区分队列满和队列空的状态

5. 虚函数在性能敏感场景下有什么替代方案?

答:虚函数的开销:间接寻址(通过 vptr 找 vtable 再找函数)、无法内联、可能导致 icache miss。

替代方案:

CRTP(奇异递归模板模式):

template<typename Derived>
class Base {
public:
    void process() {
        static_cast<Derived*>(this)->processImpl(); // 编译期确定,可内联
    }
};

class Derived : public Base<Derived> {
public:
    void processImpl() { /* 具体实现 */ }
};

  • 编译期多态,零运行时开销,可以内联
  • 缺点:不能把不同类型放在同一个容器里,失去运行时多态能力

std::variant + std::visit

using Shape = std::variant<Circle, Rectangle, Triangle>;
std::vector<Shape> shapes;

for (auto& s : shapes) {
    std::visit([](auto& shape) { shape.draw(); }, s);
}

  • 编译期生成跳转表,比虚函数快
  • 类型集合固定,不能运行时扩展

函数指针 / std::function

  • 函数指针开销和虚函数类似,但没有 vtable 查找
  • std::function 有额外的类型擦除开销,比虚函数还慢,不推荐热路径使用

实际选择:

  • 自动驾驶感知模块的热路径(每帧处理)用 CRTP 或直接模板
  • 插件系统、策略模式等需要

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C++八股文全集 文章被收录于专栏

本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。

全部评论

相关推荐

03-10 00:15
已编辑
快手_测试开发(实习员工)
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务