嵌入式开发面试八股文详解教程

嵌入式开发面试八股文详解教程

目录


C语言基础

1. 内联函数(inline)

概念

内联函数是一种特殊的函数声明方式,通过在函数前面加上 inline 关键字来实现。

工作原理

  • 当调用内联函数时,编译器会将函数代码直接展开到调用处,而不是进行常规的函数跳转
  • 普通函数调用需要保存局部变量和计算值到栈中,然后跳转到函数执行,执行完毕后再返回原位置
  • 内联函数直接替换代码,避免了函数跳转的开销

优点

  • 减少函数调用开销
  • 提高程序执行效率

示例

c 复制代码
inline int add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(3, 5);  // 编译器会将add函数直接展开
    // 相当于:int result = 3 + 5;
    return 0;
}

2. strcpy 函数实现

函数原型

c 复制代码
char* strcpy(char* dest, const char* src);

功能

  • 将源字符串(src)复制到目标字符串(dest)
  • 返回目标字符串的起始地址

实现代码

c 复制代码
char* my_strcpy(char* dest, const char* src) {
    char* ret = dest;  // 保存目标地址用于返回
    
    // 使用while循环复制字符,直到遇到结束符'\0'
    while ((*dest++ = *src++) != '\0');
    
    return ret;
}

注意事项

  • 需要确保目标缓冲区有足够空间
  • 会自动复制结束符 \0

3. strcmp 函数实现

函数原型

c 复制代码
int strcmp(const char* str1, const char* str2);

功能

  • 比较两个字符串是否相等
  • 返回值:相等返回0,str1大于str2返回正数,小于返回负数

实现代码

c 复制代码
int my_strcmp(const char* str1, const char* str2) {
    while (*str1 && (*str1 == *str2)) {
        str1++;
        str2++;
    }
    return *(unsigned char*)str1 - *(unsigned char*)str2;
}

4. strcat 函数实现

函数原型

c 复制代码
char* strcat(char* dest, const char* src);

功能

  • 将源字符串追加到目标字符串末尾
  • 返回连接后的字符串地址

实现思路

  1. 定位到目标字符串的结尾处
  2. 从源字符串的头部开始复制
  3. 复制直到遇到源字符串的结束符

实现代码

c 复制代码
char* my_strcat(char* dest, const char* src) {
    char* ret = dest;
    
    // 定位到dest的结尾
    while (*dest != '\0') {
        dest++;
    }
    
    // 将src追加到dest后面
    while ((*dest++ = *src++) != '\0');
    
    return ret;
}

程序编译与内存

5. 程序的内存分段

程序在内存中主要分为以下几个段:

1. 代码段(Text Segment)
  • 作用:存储程序的可执行指令
  • 特性:一般为只读,防止被意外修改
2. 数据段(Data Segment)
  • 作用:存储已初始化的全局变量和静态变量
  • 示例int global_var = 10;
3. BSS段
  • 作用:存储未初始化的全局变量和静态变量
  • 特性:初始值默认为0
  • 示例int global_var;
4. 堆(Heap)
  • 作用:动态内存分配
  • 管理 :使用 mallocfree 进行管理
  • 特性:由程序员手动管理
5. 栈(Stack)
  • 作用:存储局部变量、函数参数、返回地址
  • 管理:由操作系统自动管理
  • 特性:先进后出(LIFO)

内存布局图

复制代码
高地址
|--------|
|  栈    | ← 向下增长
|--------|
|   ↓    |
|        |
|   ↑    |
|--------|
|  堆    | ← 向上增长
|--------|
| BSS段  |
|--------|
| 数据段 |
|--------|
| 代码段 |
|--------|
低地址

6. C文件编译为可执行程序的过程

一个 .c 文件转化为可执行程序需要经过四个步骤:

步骤1:预处理(Preprocessing)
  • 操作:展开头文件、宏定义,删除注释
  • 输出.i 文件
  • 命令gcc -E source.c -o source.i
步骤2:编译(Compilation)
  • 操作:将预处理后的源代码转换为汇编代码
  • 输出.s 文件
  • 命令gcc -S source.i -o source.s
步骤3:汇编(Assembly)
  • 操作:将汇编代码转换为机器码
  • 输出.o 目标文件
  • 命令gcc -c source.s -o source.o
步骤4:链接(Linking)
  • 操作:将所有目标文件链接成一个可执行程序
  • 输出:可执行文件
  • 命令gcc source.o -o program

完整流程图

复制代码
source.c → [预处理] → source.i → [编译] → source.s 
         → [汇编] → source.o → [链接] → executable

7. 大小端存储

概念

  • 大端存储:高字节存储在低地址
  • 小端存储:低字节存储在低地址

示例

对于数据 0x12345678

地址 大端存储 小端存储
0x00 0x12 0x78
0x01 0x34 0x56
0x02 0x56 0x34
0x03 0x78 0x12

判断系统大小端的代码

c 复制代码
#include <stdio.h>

int main() {
    unsigned int num = 0x12345678;
    unsigned char* ptr = (unsigned char*)&num;
    
    if (*ptr == 0x78) {
        printf("小端存储\n");
    } else if (*ptr == 0x12) {
        printf("大端存储\n");
    }
    
    return 0;
}

数据结构

8. 栈与队列的区别

特性 栈(Stack) 队列(Queue)
访问方式 先进后出(LIFO) 先进先出(FIFO)
操作方式 只能在栈顶操作 队尾插入,队首删除
应用场景 函数调用、表达式求值 任务调度、消息队列、广度优先搜索

栈的应用示例

c 复制代码
// 函数调用栈
void func3() { /* ... */ }
void func2() { func3(); }
void func1() { func2(); }
int main() { func1(); }
// 调用顺序:main → func1 → func2 → func3

队列的应用示例

c 复制代码
// 任务调度
任务1 → 任务2 → 任务3 → [队列] → 执行任务1 → 执行任务2 → 执行任务3

9. 堆与栈的区别

特性 堆(Heap) 栈(Stack)
管理方式 手动管理(malloc/free) 自动管理
生长方向 向上增长(低→高地址) 向下增长(高→低地址)
大小限制 较大,受系统内存限制 较小,通常几MB
分配效率 较慢 较快
碎片问题 可能产生内存碎片 不会产生碎片

网络通信

10. TCP 与 UDP 的区别

特性 TCP UDP
连接方式 面向连接 无连接
可靠性 可靠传输,保证数据正确到达 不可靠传输
传输方式 面向字节流 面向数据报
速度 较慢 较快
应用场景 HTTP、FTP、邮件 视频流、DNS
流量控制 有流量控制和拥塞控制

11. TCP 通信流程

TCP 客户端流程:
  1. 创建套接字socket()
  2. 连接服务器connect()
  3. 发送/接收数据send()/recv()write()/read()
  4. 关闭连接close()
TCP 服务端流程:
  1. 创建套接字socket()
  2. 绑定地址bind()
  3. 监听连接listen()
  4. 接受连接accept()
  5. 发送/接收数据send()/recv()write()/read()
  6. 关闭连接close()

示例代码

c 复制代码
// TCP服务端
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
bind(sockfd, ...);
listen(sockfd, 5);
int client_fd = accept(sockfd, ...);
recv(client_fd, buffer, ...);
close(client_fd);

12. UDP 通信流程

UDP 客户端流程:
  1. 创建套接字socket()
  2. 发送数据sendto()
  3. 接收数据recvfrom()
  4. 关闭套接字close()
UDP 服务端流程:
  1. 创建套接字socket()
  2. 绑定地址bind()
  3. 接收数据recvfrom()
  4. 发送数据sendto()
  5. 关闭套接字close()

特点

  • UDP不需要建立连接,直接发送数据
  • 使用 sendto()recvfrom() 进行通信

操作系统

13. 进程与线程的区别

特性 进程 线程
资源占用 独立的地址空间 共享进程的地址空间
开销 创建、切换开销大 创建、切换开销小
通信方式 IPC(管道、消息队列等) 共享内存,通信简单
独立性 相互独立 属于同一进程

14. 进程间通信方式

  1. 管道(Pipe)

    • 半双工通信
    • 只能在具有亲缘关系的进程间使用
  2. 命名管道(FIFO)

    • 可在无亲缘关系的进程间通信
  3. 消息队列

    • 在内核中创建消息链表
    • 克服信号传递信息少的缺点
  4. 共享内存

    • 最快的IPC方式
    • 在内核空间创建共享内存区域
  5. 信号量

    • 用于进程同步
  6. 信号

    • 用于通知进程某个事件已发生
  7. 套接字(Socket)

    • 可用于不同机器间的进程通信

15. 进程的五种状态

  1. 创建状态

    • 进程正在被创建
  2. 就绪状态

    • 进程获得了除CPU外的所有资源,等待CPU分配
  3. 运行状态

    • 进程正在CPU上执行
  4. 阻塞状态

    • 进程等待某个事件发生(如I/O完成)
  5. 终止状态

    • 进程执行完毕或异常终止

状态转换图

复制代码
创建 → 就绪 ⇄ 运行 → 终止
        ↓     ↑
        阻塞 ←┘

16. CPU调度算法

  1. 先来先服务(FCFS)

    • 按到达顺序调度
    • 简单但可能造成长作业等待
  2. 短作业优先(SJF)

    • 优先调度执行时间最短的作业
    • 可能导致长作业饥饿
  3. 时间片轮转

    • 每个进程分配固定时间片
    • 时间片用完则切换到下一个进程
  4. 优先级调度

    • 根据优先级调度
    • 可能导致低优先级进程饥饿

17. 系统调用

概念

系统调用是用户空间与内核空间之间的接口,通过这个接口可以使用户空间访问到内核空间。

为什么需要系统调用

  • 直接访问内核空间非常不安全
  • 恶意用户可能修改内核内容导致内核崩溃
  • 系统调用提供了安全的访问机制

调用流程

复制代码
用户空间                  内核空间
应用程序
  ↓
open/read/write  →  系统调用接口
                    ↓
                  sys_open/sys_read/sys_write

常见系统调用

  • 文件操作:open(), read(), write(), close()
  • 进程控制:fork(), exec(), wait()
  • 内存管理:mmap(), brk()

嵌入式通信协议

18. SPI 与 I²C 寻址方式的区别

SPI 寻址方式:
  • 线路:4根线(MOSI、MISO、SCLK、CS)
  • 寻址:通过CS片选信号选择对应设备
  • 特点:支持多从设备,每个设备需要独立的CS线
I²C 寻址方式:
  • 线路:2根线(SDA数据线、SCL时钟线)
  • 寻址:通过设备地址进行寻址(7位或10位地址)
  • 特点:所有设备共享总线,通过地址区分

对比表

特性 SPI I²C
线路数量 4根 2根
寻址方式 片选信号 设备地址
速度 较快 较慢
硬件复杂度 较高 较低

19. FIFO(队列缓冲区)

概念

FIFO是一种先进先出的缓冲区,常用于数据传输和任务调度。

实现方式

  1. 软件实现:使用环形缓冲区
  2. 硬件实现:高端芯片自带FIFO硬件

应用场景

  • 串口通信
  • 数据采集
  • 任务队列

使用FIFO的优点

复制代码
传统方式:
CPU → 取数据 → 发送到串口 → 等待发送完成(阻塞其他任务)

使用FIFO:
CPU → 数据放入FIFO → 继续执行其他任务
        ↓
      FIFO → 串口发送(后台进行)
  • 减轻CPU负担
  • 提高系统响应速度
  • 避免数据丢失

20. 交叉编译

概念

交叉编译是指在一个平台上编译出另一个平台的程序。

应用场景

  • 在Ubuntu(x86)上编译ARM平台的程序
  • 开发板上通常没有编译工具链

示例

bash 复制代码
# 在Ubuntu上使用交叉编译工具链
arm-linux-gnueabihf-gcc main.c -o main

# 将编译好的程序拷贝到ARM开发板运行
scp main root@192.168.1.100:/home/

为什么需要交叉编译

  • 嵌入式设备资源有限,无法运行编译器
  • 在PC上编译速度更快
  • 便于开发和调试

21. DMA(直接内存访问)

概念

DMA是一种允许外设直接访问内存的技术,无需CPU介入。

传统方式 vs DMA

传统方式

复制代码
外设 → CPU读取 → 内存
     ← CPU写入 ←

使用DMA

复制代码
外设 ←→ DMA控制器 ←→ 内存
CPU只需配置,数据传输自动完成

优点

  • 减轻CPU负担
  • 提高数据传输效率
  • CPU可以并行处理其他任务

应用场景

  • 大量数据传输
  • 高速串口通信
  • ADC数据采集

C++面向对象

22. 构造函数与析构函数

构造函数(Constructor)

作用

  • 初始化对象
  • 设置初始值

特性

  1. 名称与类名相同
  2. 没有返回值
  3. 可以有多个重载版本(支持不同初始化方式)

示例

cpp 复制代码
class Student {
private:
    string name;
    int age;
    
public:
    // 默认构造函数
    Student() {
        name = "Unknown";
        age = 0;
    }
    
    // 带参数的构造函数
    Student(string n, int a) {
        name = n;
        age = a;
    }
    
    // 拷贝构造函数
    Student(const Student& other) {
        name = other.name;
        age = other.age;
    }
};
析构函数(Destructor)

作用

  • 释放资源
  • 执行清理操作

特性

  1. 名称为 ~类名
  2. 没有参数
  3. 没有返回值
  4. 不能重载(每个类只能有一个析构函数)

示例

cpp 复制代码
class FileHandler {
private:
    FILE* file;
    
public:
    FileHandler(const char* filename) {
        file = fopen(filename, "r");
    }
    
    // 析构函数:关闭文件
    ~FileHandler() {
        if (file != nullptr) {
            fclose(file);
            file = nullptr;
        }
    }
};

总结

本教程涵盖了嵌入式开发面试中的核心知识点:

  1. C语言基础:内联函数、字符串操作函数实现
  2. 程序编译与内存:内存分段、编译流程、大小端存储
  3. 数据结构:栈、队列、堆的区别与应用
  4. 网络通信:TCP/UDP协议及通信流程
  5. 操作系统:进程、线程、IPC、调度算法、系统调用
  6. 嵌入式通信:SPI、I²C、FIFO、DMA、交叉编译
  7. C++面向对象:构造函数与析构函数
相关推荐
蛐蛐蛐2 小时前
Win11上VS Code免输密码连接Ubuntu的正确设置方法
linux·运维·ubuntu
Flower#2 小时前
【算法】树上启发式合并 (CCPC2020长春 F. Strange Memory)
c++·算法
程序新视界2 小时前
面试中,如何筛选合格的人才?
面试·程序员
奔跑吧邓邓子2 小时前
【C++实战(75)】筑牢安全防线,攻克漏洞难题
c++·安全·实战·漏洞
七七七七072 小时前
【Linux 系统】理解Linux下一切皆文件
linux·运维·服务器
聪明的笨猪猪3 小时前
Java “线程池(1)”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
karry_k3 小时前
ThreadLocal原理以及内存泄漏
java·后端·面试
胖咕噜的稞达鸭3 小时前
二叉树进阶面试题:最小栈 栈的压入·弹出序列 二叉树层序遍历
开发语言·c++