联想嵌入式软件开发 二面 面经

1.自我介绍

面试官好,我换个角度介绍一下自己。一面主要聊了C++基础知识,这次我想说说我对嵌入式开发的理解和实践经验。

我认为嵌入式开发的核心是在资源受限的环境下实现功能,这需要对性能、内存、功耗等多个维度进行权衡。在我的项目中,我不仅要实现功能,还要考虑代码效率、内存占用、启动时间等指标,这让我养成了精细化编程的习惯。

我选择嵌入式这个方向,是因为喜欢软硬结合、能看到实际产品的感觉。相比纯软件开发,嵌入式更贴近硬件,需要理解底层原理,这种技术深度很吸引我。

联想在PC和智能设备领域有深厚的技术积累,我希望能在这个平台上,将技术应用到实际产品中,也在这个过程中不断成长。

2.说说你对内存管理的理解,如何避免内存泄漏?

内存管理是嵌入式开发的核心问题,因为资源有限,内存泄漏可能导致系统崩溃。

内存分配方式有三种:栈分配、堆分配、静态分配。栈分配速度快,自动释放,但空间有限;堆分配灵活,但需要手动管理;静态分配在编译期确定,整个程序运行期间都存在。

void func() {
    int stack_var;              // 栈分配,函数返回时自动释放
    int* heap_var = new int;    // 堆分配,需要手动delete
    static int static_var;      // 静态分配,程序结束时释放
    
    delete heap_var;            // 必须手动释放
}


避免内存泄漏的方法:

使用智能指针,自动管理内存生命周期。unique_ptr用于独占所有权,shared_ptr用于共享所有权,避免手动delete。

void func() {
    std::unique_ptr<int> ptr = std::make_unique<int>(10);
    // 函数返回时自动释放,不需要delete
}


使用RAII原则,资源获取即初始化。将资源管理封装在对象中,利用构造函数获取资源,析构函数释放资源。

class FileHandler {
    FILE* file;
public:
    FileHandler(const char* filename) {
        file = fopen(filename, "r");
    }
    ~FileHandler() {
        if (file) fclose(file);
    }
};


避免循环引用,shared_ptr的循环引用会导致内存泄漏。使用weak_ptr打破循环。

class Node {
public:
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // 用weak_ptr避免循环引用
};


使用内存检测工具,Valgrind、AddressSanitizer等工具可以检测内存泄漏、越界访问等问题。

# 使用Valgrind检测内存泄漏
valgrind --leak-check=full ./myapp

# 使用AddressSanitizer
g++ -fsanitize=address -g main.cpp -o myapp
./myapp


嵌入式开发中的特殊考虑:

  • 尽量使用栈分配,避免堆分配的碎片化
  • 使用内存池,预分配固定大小的内存块
  • 定期检查内存使用情况,设置内存上限告警
  • 长时间运行的系统,要特别注意内存泄漏

3.说说你对多线程编程的理解,如何保证线程安全?

多线程编程可以提高程序的并发性能,但也带来了数据竞争、死锁等问题。

线程安全的核心是保护共享数据。当多个线程访问同一数据时,需要同步机制保证数据一致性。

互斥锁(mutex)是最基本的同步机制,保证同一时刻只有一个线程访问临界区。

std::mutex mtx;
int shared_data = 0;

void thread_func() {
    std::lock_guard<std::mutex> lock(mtx);  // RAII管理锁
    shared_data++;  // 临界区
}


读写锁(shared_mutex)适合读多写少的场景,多个线程可以同时读,但写时独占。

std::shared_mutex rw_mtx;
int shared_data = 0;

void reader() {
    std::shared_lock<std::shared_mutex> lock(rw_mtx);  // 共享锁
    int value = shared_data;  // 读操作
}

void writer() {
    std::unique_lock<std::shared_mutex> lock(rw_mtx);  // 独占锁
    shared_data++;  // 写操作
}


原子操作(atomic)适合简单的数据类型,不需要锁,性能更好。

std::atomic<int> counter(0);

void thread_func() {
    counter++;  // 原子操作,不需要锁
}


条件变量(condition_variable)用于线程间通信,一个线程等待条件满足,另一个线程通知条件已满足。

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void wait_thread() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; });  // 等待ready为true
    // 执行任务
}

void notify_thread() {
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();  // 通知等待的线程
}


避免死锁的方法:

  • 统一加锁顺序,所有线程按相同顺序获取锁
  • 使用std::lock同时锁多个互斥量
  • 使用超时锁,避免永久等待
  • 减小锁的粒度,只在必要时加锁
// 避免死锁:统一加锁顺序
std::mutex mtx1, mtx2;

void thread1() {
    std::lock(mtx1, mtx2);  // 同时锁两个互斥量
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
    // 临界区
}


嵌入式开发中的考虑:

  • 线程数量要控制,避免过多线程导致上下文切换开销
  • 优先使用无锁数据结构,减少锁竞争
  • 实时系统中,要考虑优先级反转问题
  • 使用线程池,避免频繁创建销毁线程

4.如何进行性能优化?有哪些常用的方法?

性能优化要遵循"先测量,后优化"的原则,不要过早优化。

算法优化是最根本的,选择合适的算法和数据结构。比如用哈希表替代线性查找,时间复杂度从O(n)降到O(1);用二分查找替代遍历,从O(n)降到O(log n)。

// 不好的做法:线性查找
bool find_in_vector(const std::vector<int>& vec, int target) {
    for (int x : vec) {
        if (x == target) return true;
    }
    return false;
}

// 好的做法:使用哈希表
std::unordered_set<int> set(vec.begin(), vec.end());
bool found = set.count(target) > 0;


减少内存分配,频繁的new/delete开销很大。使用对象池、内存池,或者预分配空间。

// 不好的做法:频繁分配
for (int i = 0; i < 1000; i++) {
    int* p = new int;
    // 使用p
    delete p;
}

// 好的做法:预分配
std::vector<int> vec;
vec.reserve(1000);  // 预分配空间
for (int i = 0; i < 1000; i++) {
    vec.push_back(i);
}


缓存友好,现代CPU有多级缓存,连续内存访问比跳跃访问快得多。优先使用数组而非链表,数据结构要考虑缓存行对齐。

// 缓存友好:连续访问
std::vector<int> vec(1000000);
for (int i = 0; i < vec.size(); i++) {
    vec[i] = i;  // 连续访问,缓存命中率高
}

// 缓存不友好:跳跃访问
std::list<int> list;
for (int x : list) {
    // 链表节点分散在内存中,缓存命中率低
}


减少函数调用开销,对于小函数,使用inline减少调用开销。对于虚函数,如果能确定类型,避免虚函数调用。

// 使用inline
inline int add(int a, int b) {
    return a + b;
}

// 避免虚函数调用
Base* ptr = new Derived();
ptr->func();  // 虚函数调用,有vtable查找开销

Derived* ptr2 = new Derived();
ptr2->func();  // 直接调用,没有虚函数开销


编译器优化,使用-O2或-O3优化选项,使用-march指定目标架构,使用PGO(Profile-Guided Optimization)。

# 编译优化
g++ -O3 -march=native main.cpp -o myapp

# PGO优化
g++ -fprofile-generate main.cpp -o myapp
./myapp  # 运行收集profile数据
g++ -fprofile-use main.cpp -o myapp


并行化,使用多线程或SIMD指令加速计算密集型任务。

// 使用OpenMP并行化
#p

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

嵌入式面试八股文全集 文章被收录于专栏

这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。

全部评论

相关推荐

最喜欢秋天的火龙果很...:第一份工作一定要往大的去,工资低点没事。后面换工作会更好找,即使你去小公司,你也不可能不会换工作的。所以找大的去
点赞 评论 收藏
分享
三分入剑:我觉得还是学历问题 如果你真的想要进大厂不想在小厂的话读个211得研究生吧 我感觉简历还没你好呢 我都实习了俩月了 我投了一百多份能投出20多份简历 能面试六七次 我们部门只招研究生了都 现在连9本都很难找到像样的大厂了 你又没打过rm这种 我觉得想要进步的话就考个研究生吧
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

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