C语言 链表 函数指针 指针函数
## 一、链表Linked List### 1.1 知识概述**链表是什么**一种通过指针将零散的内存节点串起来的数据结构。每个节点包含数据和指向下一个节点的指针。**为什么用链表 vs 数组**| | 数组 | 链表 ||---|------|------|| 内存 | 连续分配大小固定 | 零散分配动态增减 || 访问 | O(1) 随机访问 arr[i] | O(n) 顺序遍历 || 插入/删除 | O(n) 需移动元素 | O(1) 改指针 || 适用场景 | 固定大小、频繁随机读 | 动态变化、频繁增删 |**关键概念**- **单向链表**只有 next 指针- **双向链表**有 next 和 prev 指针- **循环链表**尾节点 next 指向头节点- **侵入式链表Intrusive**链表节点嵌入到数据结构内部Linux 内核风格---### 1.2 项目实战分析本项目 Boot/public/list.h 使用了 **Linux 内核经典侵入式双向循环链表**c// 链表节点本身不存数据只存指针struct list_head {struct list_head *next, *prev;};// 使用方法把 list_head 嵌入到你的业务结构体里typedef struct SYS_TIMER_HANDLE {SYS_OBJECT_S stObject; // 对象管理SYS_LIST_NODE_S stTimerList; // ← 链表节点嵌入结构体USER_TIMER_S stUserTimer; // 用户参数u32 u32TimeOutTick;} SYS_TIMER_HANDLE_S;**为什么用侵入式链表**- 一个结构体可以被多个链表管理如一个定时器既在定时链表又在对象链表只要嵌多个 list_head 即可- 无需为每个节点单独 malloc 链表节点内存省内存- 这是嵌入式资源受限场景常用做法**核心难点container_of 宏**——从链表节点反推整个结构体c// 获取结构体成员在结构体内的偏移量#define offsetof(TYPE, MEMBER) ((size_t) ((TYPE *)0)-MEMBER)// 从成员指针反推整个结构体起始地址#define container_of(ptr, type, member) ({ \(type *)( (char *)ptr - offsetof(type,member) );})// 实际使用拿到链表节点 → 还原成整个结构体pstSysTimer (pSYS_TIMER_HANDLE_S)SYS_CONTAINER_OF(s_stTimerListHead.next, // 链表第一个节点地址SYS_TIMER_HANDLE_S, // 结构体类型stTimerList); // 结构体里的成员名**图解 container_of 原理**结构体起始地址(未知) ──┐│ 偏移 offsetof(type,member)▼┌─────────────────────┐│ ...stObject... ││ ...stTimerList... ← 我们知道这个成员的地址(ptr)│ ...u32TimeOutTick │└─────────────────────┘结构体地址 ptr - offsetof(type, member)**遍历宏**c// 普通遍历不能删除当前节点#define list_for_each(pos, head) \for (pos (head)-next; pos ! (head); pos pos-next)// 安全遍历可以先删除当前节点因为有n保存了下一个#define list_for_each_safe(pos, n, head) \for (pos (head)-next, n pos-next; pos ! (head); \pos n, n pos-next)// 为什么要 safe因为删除当前节点后 pos-next 可能失效需提前保存 next---### 1.3 面试题**Q1: 链表和数组的区别各自优缺点**- 数组连续内存随机访问 O(1)但插入删除要移动元素 O(n)大小固定- 链表零散内存插入删除 O(1)只需改指针但随机访问要遍历 O(n)- 嵌入式常用链表因为任务/消息数量动态不定且增删频繁**Q2: 解释 container_of 宏的原理**- offsetof(TYPE, MEMBER) 利用结构体在地址0处的技巧取成员地址即偏移量- container_of (char*)ptr - offsetof(...)把成员指针往前偏移得到整个结构体地址- 这是侵入式数据结构能工作的核心**Q3: 为什么嵌入式常用侵入式链表**- 节省内存不需要额外链表节点- 一个数据可被多个链表管理mulit-list- 性能好操作直接**Q4: 如何安全地遍历并删除链表节点**- 用 list_for_each_safe(pos, n, head)因为它提前保存 n pos-next删除当前节点后还能继续- 如果用普通 list_for_each删完当前节点 pos-next 已被清空循环会崩溃**Q5: 链表插入/删除的时间复杂度**- 插入/删除已知节点O(1)改前后指针- 但如果是查找后再插入查找也要 O(n)整体 O(n)**动手练习**实现一个保存学生信息的双向循环链表支持插入/删除/遍历/查找。---## 二、队列 / FIFO### 2.1 知识概述**队列Queue**先进先出FIFO的数据结构插入在队尾tail删除在队头head。**为什么要用队列**生产者/消费者解耦。例如- 中断里收到串口数据 → 放入队列 → 主循环慢慢取出来处理- 一个模块发消息 → 放入另一个模块的消息队列 → 对方异步处理**环形队列Ring Buffer**用固定大小数组 读写指针循环利用避免频繁 malloc。---### 2.2 项目实战分析本项目 Boot/public/Sys_fifo.h 定义了**两种队列****① 通用环形队列 QUEUE_S**ctypedef struct fifo {u16 head; // 队头下标u16 tail; // 队尾下标u16 len; // 队列长度元素个数u16 size; // 队列元素大小字节BOOL bfull; // 是否满的标志解决环形队列判断满/空的歧义u8* buf; // 队列缓存}QUEUE_S;**满/空判断问题**- 如果 head tail可能是空也可能是满环形回绕时会混淆- **方案A**留一个空位head tail 判空(tail1)%len head 判满- **方案B本项目**增加 bfull 标志位专门记录是否满**② 精简 FIFO SIMPLE_FIFO_S**ctypedef struct SIMPLE_FIFO {volatile u32 u32ReadPos; // 读位置volatile u32 u32WritePos; // 写位置u32 u32Mask; // 掩码大小必须是2的幂如255u32 u32Len;BOOL bInit;u8* pu8Buf;}SIMPLE_FIFO_S;**核心优化——用 mask 代替 % len**c// 普通环形队列写法慢除法指令pos (pos 1) % len;// 2的幂大小优化快与运算// 如果 len 256mask 255pos (pos 1) 255; // 等价于 % 256但一条指令搞定**为什么可行**当 u32Len 是 2 的幂如 256、512、1024x (len-1) 等价于 x % len。嵌入式里追求性能常用这种技巧。**为什么读写位置用 volatile**cvolatile u32 u32ReadPos; // 可能被中断修改生产者volatile u32 u32WritePos; // 可能被主循环修改消费者因为一边在中断里写一边在主循环读两个位置都可能被另一方异步修改需要 volatile 防止编译器优化缓存到寄存器导致读到旧值。---### 2.3 面试题**Q1: 环形队列如何判断满和空**- 方案1留一个空位head tail 判空(tail1)%size head 判满- 方案2额外加 count 或 bfull 标志本项目 QUEUE_S.bfull- 方案3满时 head 和 tail 相差 1用差值判断**Q2: 为什么用 mask 而不是 % len**- % len 会编译成除法指令慢Cortex-M0 没有硬件除法- 如果 len 是 2 的幂 (len-1) 只需一条 AND 指令快很多- 嵌入式性能敏感场景的经典优化**Q3: 队列在嵌入式里的典型应用**- 串口/UART 接收缓冲——中断收数据入队主循环出队解析- 消息队列SoftBus 的 msgQueue 就是链表队列- AD采集数据缓冲、按键事件缓冲**Q4: 多线程/中断环境下队列如何保证安全**- 关中断__disable_irq()/__enable_irq()保护入队/出队操作- 或临界区SYS_TIMER_CRITICAL_ENTER 宏- 注意关中断时间要短否则影响实时性**Q5: 为什么 FIFO 读写用 volatile**- 防止编译器优化——编译器可能会把频繁访问的变量缓存到寄存器导致读取到旧值- volatile 告诉编译器这个变量可能被外部中断/其他线程改变每次都必须从内存读**动手练习**实现一个 SIMPLE_FIFO_S 类似的无锁环形 FIFO单生产者单消费者验证空/满判断。---## 三、函数指针 / 回调函数### 3.1 知识概述**函数指针**指向函数的指针可以像调用函数一样通过指针调用。**定义语法**c// 声明一个函数int add(int a, int b) { return ab; }// 声明一个函数指针指向 返回int、接收(int,int) 的函数int (*fp)(int, int);// 赋值并调用fp add;int result fp(3, 4); // 7**⚠️ 易混淆函数指针 vs 指针函数**cint (*fp)(int); // fp 是函数指针指向一个返回 int 的函数int *fp(int); // fp 是指针函数是一个返回 int* 的函数fp是普通函数// 关键(*fp) 括起来才是指针否则是函数**回调函数Callback**把一个函数指针作为参数传给另一个函数由被调方在合适时机调用。这是 C 语言实现面向对象/多态/事件机制的核心。---### 3.2 项目实战分析本项目大量使用函数指针实现**对象化封装**和**回调机制****① HAL 对象化——用结构体函数指针模拟类**halif_i2c.hctypedef struct I2COBJECT {// 全是函数指针相当于类的方法HALIF_StatusTypeDef (*Open)(I2C_PERIPHERAL_E);HALIF_StatusTypeDef (*Close)(I2C_PERIPHERAL_E);HALIF_StatusTypeDef (*Master_Sim_Send)(...);HALIF_StatusTypeDef (*Master_Sim_Recv)(...);HALIF_StatusTypeDef (*Master_Sim_MemWrite)(...);// ...} I2COBJECT_S;// 定义全局对象EXTERN I2COBJECT_S I2CObject;// 初始化时绑定具体实现halif_i2c.cHALIF_StatusTypeDef Hali2c_CreateObject(I2COBJECT_S* Object) {Object-Open Hali2c_Open; // 绑定实际函数Object-Master_Sim_Send Hali2c_MasterSimSend;Object-Master_Sim_MemWrite Hali2c_SimMemWrite;// ...return HALIF_OK;}// 上层调用只通过对象接口不知道底层实现多态I2CObject.Open(I2C_MCU);I2CObject.Master_Sim_MemWrite(I2C_MCU, addr, reg, 1, data, 1, timeout);**这就是用 C 模拟面向对象的多态**上层面对的是统一接口 I2CObject.xxx下层实现可以换成硬件I2C、软件模拟I2C等任意实现上层代码不用改。**② SoftBus 消息回调**SoftBus.cc// 消息处理回调类型typedef int (*msgProc)(struct SoftBusMsg*); // 请求处理typedef int (*eventProc)(struct SoftBusMsg*, void*); // 事件处理// 每个服务注册消息ID → 处理函数映射表typedef struct MsgProcMap {uint32_t msgID; // 消息IDmsgProc proc; // 函数指针指向处理函数} MsgProcMap;// 处理函数示例某个服务static int light_msg_set_duty(struct SoftBusMsg* msg) { ... }**③ 定时器回调**sys_timer.hctypedef struct USER_TIMER {void(*pTimeOutFunc_Cb)(void *pParam); // 定时器超时回调函数指针void *pParam; // 传给回调的参数u32 u32Ticks;u8 u8TimerType;}USER_TIMER_S;// 应用层注册回调USER_TIMER_S myTimer {.pTimeOutFunc_Cb myLedToggle, // 到点调用这个函数.u32Ticks 500,.u8TimerType SYS_TIMER_TYPE_PERIODIC};**④ Boot 升级回调注册**Boot/main.ccstUser.pEraseCb BspGD32E230FlashErase; // 注册Flash擦除函数stUser.pReadCb BspGD32E230FlashRead; // 注册Flash读函数stUser.pWriteCb BspGD32E230FlashWrite; // 注册Flash写函数sys_Update_Register(stUser); // 把回调注入升级模块// 升级模块不关心Flash具体怎么操作只调用 pEraseCb/pReadCb/pWriteCb---### 3.3 面试题**Q1: 函数指针和指针函数的区别**cint (*fp)(int); // 函数指针fp是指针指向返回int的函数int *fp(int); // 指针函数fp是函数返回的是int*指针关键看 *fp 是否被括号括起来。(*fp) 表示fp是指针。**Q2: 回调函数的本质是什么为什么用回调**- 本质把做什么的代码作为参数传给何时做的框架- 好处解耦——框架如定时器不关心具体业务如翻转LED只需在到点调用回调上层业务也不用关心框架内部实现**Q3: 用结构体函数指针模拟面向对象多态的原理**- 结构体装着函数指针列表相当于虚函数表/接口- 不同实现软I2C/硬I2C填充不同的函数指针- 调用者面对同一接口实际执行不同实现 多态**Q4: 函数指针数组有什么用**- 状态机stateHandler[state] 数组存每个状态的处理函数- 命令解析cmdHandler[cmd] 数组存每个命令的处理函数比 switch-case 清晰- 优点是表驱动新增状态/命令只需在数组加一项不用改逻辑**Q5: 回调函数如何避免野指针/悬空指针**- 注册时确保函数确实存在生命周期- 注销时清空回调指针赋NULL- 调用前判空if (cb) cb(arg);- 注意回调里不要做耗时/阻塞操作会卡住框架**动手练习**定义一个 BUTTON_OBJECT用函数指针实现按下/释放/长按三个状态的回调注册与调用。---## 四、枚举Enum### 4.1 知识概述**枚举**一组具名的整型常量让代码可读、可维护。cenum Color { RED, GREEN, BLUE }; // RED0, GREEN1, BLUE2**本质**枚举值其实就是 int 常量默认从0递增可显式赋值。---### 4.2 项目实战分析**① 外设枚举**HwRes_Ext.h——通过 X-Macro 自动生成c#ifdef I2C_COMPONENT_LOADEDtypedef enum I2C_PERIPHERAL {#define I2C_ENUM#include HwRes_Conf.h // 展开为: I2C_MCU, I2CE2,I2C_MAX, // I2C_MAX 记录了枚举总数最后一个值个数}I2C_PERIPHERAL_E;#endif// 展开后typedef enum I2C_PERIPHERAL {I2C_MCU, // 0I2CE2, // 1I2C_MAX, // 2 ← I2C_MAX 就是数量用于数组大小/边界检查}I2C_PERIPHERAL_E;**枚举用作数组下标/边界**halif_i2c.cc// I2C_Config 数组大小 I2C_MAXI2C_CONFIG_TYPEDEF_S I2C_Config[I2C_MAX];// 用枚举做下标语义清晰I2C_Config[I2C_MCU].ClockSpeed 200;// 越界检查if (eni2c I2C_MAX) return HALIF_ERROR; // 自动边界检查**② 消息类型枚举**SoftBus.cctypedef enum EMsgType {EMsgType_req, // 请求EMsgType_resp, // 应答EMsgType_event, // 事件} EMsgType;// 用于 switch 分支switch(msg-type) {case EMsgType_req: ...case EMsgType_resp: ...case EMsgType_event: ...}**③ I2C 状态枚举**ctypedef enum {HAL_I2C_STATE_READY, // 0 空闲HAL_I2C_STATE_BUSY_TX, // 1 发送中HAL_I2C_STATE_BUSY_RX, // 2 接收中} HAL_I2C_STATE_E;---### 4.3 面试题**Q1: 枚举和 #define 宏的区别**- 枚举编译期有类型检查、会自动递增、便于调试调试器能显示名字、能作为数组大小- 宏纯文本替换无类型检查- 有意义的常量序列状态码、外设索引用枚举更好简单的数字常量如 #define LED_ON 1用宏更简单**Q2: 枚举占几个字节**- 在 C 中一般和 int 相当4字节但取决于编译器- 可以用 typedef enum x : u8C23或编译器扩展指定小类型节省内存**Q3: 用枚举做数组下标有什么好处**- 语义清晰I2C_Config[I2C_MCU] 比 I2C_Config[0] 好懂- I2C_MAX 可自动当作数组大小、遍历范围、越界检查上限- 但如果枚举值不连续显式赋值跳过会浪费数组空间**Q4: 枚举的陷阱两个枚举值相等**cenum { A0, B0, C1 }; // AB容易出错enum { X0, X1 }; // 重定义报错// 避免确保每个枚举值唯一且用默认递增**Q5: 如何在串口/调试里打印枚举名字**- 枚举本身不能转字符串需要自己定义 静态数组 映射cconst char* i2c_name[] {I2C_MCU, I2CE2}; // 枚举下标对应名字printf(使用: %s\n, i2c_name[I2C_MCU]);- 或用 X-Macro 自动生成名字数组本项目的 #include HwRes_Conf.h 技术就是这么干的**动手练习**用枚举定义串口状态机IDLE/START/DATA/CHECK并用数组下标访问状态处理函数。---## 五、结构体 / 联合体 / typedef### 5.1 知识概述- **结构体struct**把不同类型的数据组合成一个整体类似记录或类的数据部分- **联合体union**多成员共享同一块内存同一时刻只有一个成员有效大小取最大成员- **typedef**给类型起别名让代码更简洁**⚠️ 结构体内存对齐**编译器会在成员之间填充空白字节使其地址对齐这会影响 sizeof 大小。---### 5.2 项目实战分析**① 业务结构体**sys_timer.h、SoftBus.c 等c// 定时器结构体——封装了一个定时器的所有信息typedef struct USER_TIMER {void(*pTimeOutFunc_Cb)(void *pParam); // 回调函数指针void *pParam;u32 u32Ticks;u8 u8TimerType;}USER_TIMER_S;// SoftBus 消息结构体——封装一条消息的所有信息typedef struct SoftBusMsg {uint32_t msgID;uint32_t toID;uint32_t fromID;struct { int(*respProc)(struct SoftBusMsg*); } reqInfo;struct { int(*proc)(struct SoftBusMsg*); } respInfo;struct { uint32_t evtId; } eventInfo;uint8_t type;void* data; // 私有数据} SoftBusMsg;**② typedef 别名**嵌入式全景c// 类型别名简化嵌入式代码typedef unsigned char u8; // 1字节typedef unsigned short u16; // 2字节typedef unsigned long u32; // 4字节typedef signed char s8;typedef ... s32;typedef char BOOL; // 或用 int// 结构体别名typedef struct SYS_TIMER_HANDLE { ... } SYS_TIMER_HANDLE_S;typedef SYS_TIMER_HANDLE_S* pSYS_TIMER_HANDLE_S; // 指针别名**为什么用 u8/u16/u32 而不用 int** 因为 int 大小在不同平台不同嵌入式可能是 2 字节也可能是 4 字节用 u32 明确指定宽度保证跨平台、协议解析正确。**③ 联合体union用于协议/类型转换**c// 例把4字节数据当作一个u32读取常用于解析接收缓冲typedef union {u32 u32Val; // 整体当4字节u8 u8Byte[4]; // 也可逐字节访问} U32_BYTE_UNION;// 例解析协议帧时把缓冲区指针强转成结构体指针// 需注意对齐、字节序**④ 结构体内存对齐嵌入式重点**c// 未处理对齐的例子typedef struct {u8 flag; // 偏移0u32 value; // 编译器为了对齐可能在 flag 后面填充3字节偏移变为4u16 size; // 偏移8// 实际 sizeof 可能是 12而非 1427} MY_STRUCT;// 如果这个结构体用于串口协议帧会发生问题// 发方和收方偏移对不上 → 解析错位**解决方案打包**c#pragma pack(1) // 取消对齐填充1字节对齐typedef struct {u8 flag; // 偏移0u32 value; // 偏移1u16 size; // 偏移5// sizeof 7无填充} PACKED_STRUCT;#pragma pack() // 恢复默认// 或GCC/ARM:typedef struct __attribute__((packed)) { ... } PACKED_STRUCT;**注意**packed 会牺牲访问效率非对齐访问可能慢/异常但对协议解析极其重要。---### 5.3 面试题**Q1: 结构体内存对齐规则sizeof 怎么算**- 规则每个成员的对齐要求通常 成员类型大小如 u32 要4字节对齐结构体总大小是最大对齐成员大小的整数倍- 计算步骤按成员顺序累加遇到不满足对齐时在成员间填充、在末尾补齐到整体对齐- 例子ctypedef struct {char c; // 偏移0占1int i; // 偏移4c后填充3字节占4char d; // 偏移8占1// 末尾填充到最大对齐(4)的倍数 → sizeof12} S; // sizeof(S) 12**Q2: 为什么协议帧要 packed取消对齐**- 发送方和接收方可能是不同编译器/平台对齐规则可能不同- 不打包结构体中间有填充字节发出去的数据多了填充字节收方解析错位- 打包后结构体完全按声明紧密排列双方一致**Q3: typedef struct A {} A; 和 struct A {}; 的区别**cstruct Point { int x, y; }; struct Point p; // 用时要写 struct 关键字typedef struct Point { ... } Point; Point p; // 可以直接用 Point 类型typedef 给结构体起了个不带 struct 的别名用起来更简洁。**Q4: 联合体union的应用场景**- 协议解析同一段内存按不同数据类型解释如4字节缓冲既当数组又当int- 节省内存多个互斥使用的变量共享内存- 类型转换union { float f; u32 i; } 可把float当u32看**Q5: 位域bit-field怎么用有什么注意**ctypedef struct {u8 bit0 : 1; // 占1位u8 bit1_3 : 3; // 占3位u8 reserved : 4;} BIT_FIELD;- 注意位域的跨平台行为未完全标准位序、是否能跨字节协议解析时慎用一般用按位与/或/移位更可靠**动手练习**定义 PACKED_STRUCT 的串口协议帧结构体验证 sizeof并在 union 里实现多字节解析。---## 六、宏 / 条件编译 / volatile / const / 指针### 6.1 知识概述- **宏Macro**#define 文本替换编译前处理- **条件编译**#ifdef/#ifndef/#if/#else/#endif 根据宏决定编译哪些代码- **volatile**告诉编译器这个变量会被外部修改每次都要从内存读别缓存- **const**声明不可修改常用于只读数据、防误改- **指针Pointer**存了某个变量的地址可通过 *p 访问该变量---### 6.2 项目实战分析**① 模块化裁剪条件编译**main.c、sys_timer.cc// main.c按配置创建外设对象#ifdef ADC_COMPONENT_LOADEDHalAdc_CreateObject(AdcObject); // 配置了ADC才创建#endif#ifdef I2C_COMPONENT_LOADEDHali2c_CreateObject(I2CObject); // 配置了I2C才创建#endif// sys_timer.c / sys_timer.h整个文件只有在启用定时器时才编译#ifdef TIMER_MODULE_ENABLED// ...整个定时器模块代码...#endif// 跨平台适配#if defined(STM32F4xx)// F4 特有的 Flash 驱动#elif defined(GD32E23x)// GD32 特有的 Flash 驱动#endif**意义**一个代码库通过宏配置就能适配多种 MCU、多种产品配置不编译的代码不占空间。**② volatile 的应用**Sys_fifo.h、HALc// SIMPLE_FIFO_S 的读写指针中断和主循环共享typedef struct SIMPLE_FIFO {volatile u32 u32ReadPos; // 可能被中断/主循环修改volatile u32 u32WritePos;...}SIMPLE_FIFO_S;// 典型串口中断里写主循环读// 中断处理void UART_IRQ_HANDLER() {fifo.u32WritePos; // 写方}// 主循环u8 data fifo.pu8Buf[fifo.u32ReadPos]; // 读方能读到中断写入的新值**没有 volatile 会怎样**编译器可能把 u32WritePos 优化到寄存器主循环里读到的总是旧值导致读不到中断刚写入的数据。**③ const 的应用**c// 只读查找表放在FlashROM省RAMconst I2C_CONFIG_TYPEDEF_S I2C_Config[I2C_MAX] { {7, 200, 0, 0} };// const 指针回调地址用 const 表示这个指针指向的东西不能改int (*const cb)(int); // const放在*后指针本身不能改// 函数参数用 const告诉调用者我不修改你的数据void process(const u8 *data, u32 len);**④ 指针与数组**cu8 buf[100]; // buf 是数组首地址常量不能赋值给它u8 *p buf; // p 是指针可以指向别处buf[5] *(p5); // 数组下标和指针偏移等价**⑤ X-Macro 宏高级**c// HwRes_Conf.h配置列表被多次 include// 第一次include生成枚举HwRes_Ext.h#define I2C_ENUM I2C_MCU, I2CE2,// 第二次include生成配置表HwRes_Map.h#define I2C_CONFIG {7, 200, 0, 0}, {7, 100, 0, 0},// 第三次include生成IO表HwRes_Map.h#define I2C_IOCONFIG {GPIOB,GPIO_PIN_6,...}, {GPIOA,GPIO_PIN_7,...},**本质**一份数据列表通过宏控制在多个文件中展开成不同代码枚举/数组/函数避免数据改一处漏三处。---### 6.3 面试题**Q1: volatile 的作用什么场景必须用**- 作用告诉编译器这个变量可能被外部异步修改中断、其他线程、硬件每次访问必须从内存读取不要缓存到寄存器- 必须用场景①中断和主循环共享变量 ②访问硬件寄存器 ③多线程共享变量- 反例不用 volatile死循环里读中断标志可能永远读到旧值导致死循环**Q2: const 在嵌入式里的用法**- const 全局数据放 Flash只读存储省 RAM- const 防止误改如协议配置表- const char* vs char* const vs const char* const区别指向 vs 指针本身**Q3: 指针和数组的区别**- 数组编译时分配固定内存arr 是首地址常量不能 arr- 指针可以重新指向p 可以移动- 但 *(arri) arr[i]数组名能传递给指针参数**Q4: 带参宏和函数的区别宏的陷阱**- 宏编译前文本替换无类型检查可能执行多次副作用- 函数编译成代码有类型检查只执行一次- 宏陷阱c#define SQUARE(x) x*xSQUARE(23) // 展开成 23*23 11错// 正确写法要加括号#define SQUARE(x) ((x)*(x))**Q5: #pragma pack / __attribute__((packed)) 的作用**- 取消结构体自动对齐填充使结构体紧凑存储- 用于协议帧、寄存器映射等需要精确内存布局的场景- 代价非对齐访问可能慢或在某些平台异常**Q6: 带参宏如何避免副作用举一个反例。**c#define MAX(a,b) ((a)(b)?(a):(b))int x MAX(i, j); // 副作用i j 可能被执行两次或不确定次数正确做法宏只做简单替换复杂逻辑用内联函数inline。---## 综合面试实战**Q1综合**结合你的项目讲一个你使用函数指针结构体实现多态接口隔离的例子- 参考HAL 层 I2CObject——结构体里全是函数指针Hali2c_CreateObject 绑定软I2C实现上层不管底层是软I2C还是硬I2C统一调用 I2CObject.Master_Sim_MemWrite()实现了面对接口编程。**Q2综合**你的链表在哪里用到了为什么要用侵入式- 参考软件定时器的定时链表、SoftBus 的消息队列。侵入式链表省内存、可被多链表管理配合 container_of 能从节点还原整个结构体。**Q3综合**你的队列/消息是怎么在中断和主循环之间共享的- 参考UART 中断里往 SIMPLE_FIFO_S 写入写指针1主循环读取读指针1读写指针用 volatile 防止编译器优化单生产者单消费者无需锁只做临界区保护防中断打断。**Q4综合**一个用 volatile、另一个用 const你都怎么用请举例。- volatileFIFO 读写指针、中断标志- constI2C_Config[] 只读配置表放Flash、回调函数指针 const 防止被改**Q5综合**如果你要在协议解析时用结构体怎么做才能保证跨平台一致- 用 #pragma pack(1) 打包结构体明确字节序大端/小端用手动移位处理用固定宽度的 u8/u16/u32 类型。---

相关新闻

[特殊字符] 龍魂数字檄文 · 主权宣告 ——一条路线,两套规则,三方站队,四字定局

[特殊字符] 龍魂数字檄文 · 主权宣告 ——一条路线,两套规则,三方站队,四字定局

🐉 龍魂数字檄文 主权宣告 ——一条路线,两套规则,三方站队,四字定局 DNA: #龍芯⚡️丙午癸未乙酉坤卦-DIGITAL-MANIFESTO-V1.0-UID9622 确认码: #CONFIRM🌌9622-ONLY-ONCE🧬LK9X-772Z GPG:…

2026/8/6 6:49:02 阅读更多 →
RTX加速与AI降噪:Unity光照烘焙效率与画质革命

RTX加速与AI降噪:Unity光照烘焙效率与画质革命

1. 项目概述:当RTX加速与AI降噪联手,Unity光照烘焙的质变时刻如果你是一名Unity技术美术或者负责项目视觉质量的开发者,一定对“烘焙”这个词又爱又恨。爱的是,它能把复杂的光影计算提前完成,让实时渲染性能飞升&#…

2026/8/6 6:49:02 阅读更多 →
从LFI到Root:一次完整渗透测试实战流程深度解析

从LFI到Root:一次完整渗透测试实战流程深度解析

1. 项目概述:一次完整的渗透测试实战复盘最近在整理渗透测试的实战笔记,翻到了几年前在VulnHub上练习的“NullByte”靶机。这虽然是一个老靶机,但它的设计非常经典,几乎涵盖了从信息收集、Web渗透、权限提升到最终获取Root权限的完…

2026/8/6 6:49:02 阅读更多 →

最新新闻

Node.js保姆级安装配置指南:从零搭建高效开发环境

Node.js保姆级安装配置指南:从零搭建高效开发环境

1. 从“Hello World”到现代Web开发:为什么你需要Node.js如果你刚开始接触Web开发,或者想从Java、Python、PHP这些后端语言拓展到全栈,那么Node.js几乎是你绕不开的一站。它不是一个全新的编程语言,而是让JavaScript这个你原本只在…

2026/8/6 7:30:26 阅读更多 →
如何5分钟配置Unity游戏翻译神器:XUnity.AutoTranslator终极指南

如何5分钟配置Unity游戏翻译神器:XUnity.AutoTranslator终极指南

如何5分钟配置Unity游戏翻译神器:XUnity.AutoTranslator终极指南 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator 还在为外语游戏中的菜单和对话感到困惑吗?XUnity.AutoTranslator是…

2026/8/6 7:30:26 阅读更多 →
NGA论坛优化摸鱼体验:彻底改变你的论坛浏览方式

NGA论坛优化摸鱼体验:彻底改变你的论坛浏览方式

NGA论坛优化摸鱼体验:彻底改变你的论坛浏览方式 【免费下载链接】NGA-BBS-Script NGA论坛增强脚本,给你完全不一样的浏览体验 项目地址: https://gitcode.com/gh_mirrors/ng/NGA-BBS-Script 你是否遇到过这样的烦恼?在NGA论坛浏览帖子…

2026/8/6 7:30:26 阅读更多 →
API设计新思维:用流畅接口构造内部DSL

API设计新思维:用流畅接口构造内部DSL

API设计新思维:用流畅接口构造内部DSL 在传统的API设计中,我们习惯用“名词动词”的方式组织方法调用,比如 user.getAddress() 或 order.calculateTotal()。但这种方式在面对复杂业务规则时,往往会让调用代码显得冗长且难以阅读。…

2026/8/6 7:30:26 阅读更多 →
电竞比赛主板选购指南:在多显卡需求与品牌特色间找到高性价比之选

电竞比赛主板选购指南:在多显卡需求与品牌特色间找到高性价比之选

电竞比赛对主板的要求正变得越来越专业。赛场之上,供电模组的瞬时响应能力直接决定团战帧率是否波动,板载网卡的数据包优先级处理机制影响着技能释放的毫秒级延迟,而PCIe通道的带宽分配则决定了多显卡配置能否真正发挥协同效能。与此同时&…

2026/8/6 7:30:26 阅读更多 →
深入解析ADC量化误差:从原理到工程实践

深入解析ADC量化误差:从原理到工程实践

1. 从一次采样失真说起:为什么你的ADC读数总在跳?最近在调试一个高精度温度监测项目,用的是STM32F407的ADC。硬件上,分压电阻、基准电压、滤波电容都按手册精心设计,软件里也做了滑动平均滤波。但实际跑起来&#xff0…

2026/8/6 7:29:26 阅读更多 →

日新闻

深入解析LimboAI C++内核:架构设计与性能优化实战

深入解析LimboAI C++内核:架构设计与性能优化实战

1. 项目概述:为什么我们需要深入LimboAI的C内核?如果你是一名使用Godot引擎的游戏开发者,尤其是对AI行为逻辑有较高要求的项目,那么LimboAI这个名字你大概率不会陌生。它作为Godot 4生态中一个备受瞩目的行为树与状态机插件&#…

2026/8/6 0:00:06 阅读更多 →
Unity 2D游戏敌人AI系统:基于PlayMaker状态机与2D Toolkit的实战开发

Unity 2D游戏敌人AI系统:基于PlayMaker状态机与2D Toolkit的实战开发

1. 项目概述与核心思路大家好,我是老张,一个在游戏开发一线摸爬滚打了十多年的老码农。今天咱们接着聊《空洞骑士》风格2D动作游戏的Demo制作。上一期我们搭好了基础框架,处理了角色移动和碰撞,这一期,我们要让游戏世界…

2026/8/6 0:00:06 阅读更多 →
被动防火门市场前景发展趋势

被动防火门市场前景发展趋势

被动防火门依靠材质结构、密闭构造阻隔烟火蔓延,无需电控启动,是建筑被动消防系统核心构件,行业依托新规管控、城市更新、工业安全升级迎来稳定扩容,整体朝着合规化、专项化、低碳化、智能化方向发展。现阶段 GB12955‑2024 新版国…

2026/8/6 0:00:06 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/5 15:00:43 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/5 13:13:56 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/5 10:20:36 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/5 23:28:39 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/5 21:00:14 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/5 23:46:51 阅读更多 →