3.4 使用动态内存
本节导读
写程序时,我们常常事先不知道要存多少数据——今天存 3 个,明天可能存 100 个。怎么办?
答案:动态内存——在程序运行的过程中,按实际需要随时"借"、用完再"还"。这一节我们看 Z-Stack 怎么借、怎么还,以及借了不还的下场。
一、什么是动态内存?
在编写程序时,开发者可能不能确定需要使用多少内存来保存数据,希望在程序运行的过程中根据实际需要来使用相应的内存空间。这种能够在程序运行过程中申请和释放的内存空间称为动态内存。
生活类比:静态内存像买房子——面积买死了,住不满浪费、不够住尴尬;动态内存像住酒店——按晚开房(申请)、退房(释放),住几晚开几晚,灵活又省心。
Z-Stack 3.0 中动态内存分配的 API 在 OSAL_Memory.h 文件中(OSAL 目录下):

二、申请与释放:一对"黄金搭档"
在 OSAL_Memory.h 中可以找到两个最重要的 API:
/**
* @brief 动态申请内存空间
* @param size - 申请多少个字节的内存空间
* @return 返回该内存空间的指针
*/
void *osal_mem_alloc( uint16 size );
/**
* @brief 动态释放内存空间
* @param ptr - 待释放的内存空间指针
*/
void osal_mem_free( void *ptr );使用规则只有一条,但必须刻在脑子里:
⚠️ 铁律:申请的内存空间不用了,一定要调用
osal_mem_free()释放!
借了不还(不释放)会怎样? 内存空间会越来越少,程序跑着跑着就会内存不足、甚至崩溃——这就是传说中的内存泄漏。酒店房间被你占着不退,后来的客人就没房住了。
三、内存操作:借到之后怎么用
申请完动态内存,可以用内存操作 API 来使用它。最常用的两个定义在 OSAL.h 中:
/**
* @brief 把源内存空间的内容复制到目标内存空间
* @param dest - 目标内存空间
* @param src - 源内存空间
* @param len - 复制多少个字节
*/
void *osal_memcpy(void *dest, const void GENERIC *src, unsigned int len);
/**
* @brief 把从 dest 起的 len 个字节的值都设置为 value
* @param dest - 内存空间
* @param value - 指定的值
* @param len - 要设置的字节数
*/
void *osal_memset(void *dest, uint8 value, int len);| API | 作用 | 类比 |
|---|---|---|
osal_memcpy | 把一块内存的内容搬到另一块 | 把 A 文件的内容复印到 B 文件 |
osal_memset | 把一块内存全部设成同一个值 | 把房间全部刷成同一种颜色(常用 0 清零) |
四、实战:借内存打印 "Hello World!"(每 3 秒一次)
把 3.3 节的定时器事件和动态内存组合起来,完整流程走一遍。
在事件处理函数中:
if (events & SAMPLEAPP_TEST_EVT) {
// 源字符串
char *str = "Hello World!\n";
// ① 从堆空间中申请 32 个字节的内存
char *mem = osal_mem_alloc(32);
// ② 如果申请成功
if (mem != NULL) {
osal_memset(mem, 0, 32); // ③ 先把内存清零(干净的房间)
osal_memcpy(mem, str, osal_strlen(str)); // ④ 把字符串拷贝进去
printf(mem); // ⑤ 打印内存里的内容
osal_mem_free(mem); // ⑥ ★ 用完必须释放!★
}
// ⑦ 重新触发事件:3 秒后再执行一次
osal_start_timerEx(zclSampleSw_TaskID, SAMPLEAPP_TEST_EVT, 3000);
// ⑧ 清除已处理的事件,返回未处理的事件
return (events ^ SAMPLEAPP_TEST_EVT);
}这段代码干了什么?
| 步骤 | 代码 | 大白话 |
|---|---|---|
| ① | osal_mem_alloc(32) | 开房:借 32 字节 |
| ③ | osal_memset(mem, 0, 32) | 保洁:先清零 |
| ④ | osal_memcpy(...) | 入住:把字符串放进去 |
| ⑤ | printf(mem) | 展示:打印出来 |
| ⑥ | osal_mem_free(mem) | 退房:释放! |
| ⑦ | osal_start_timerEx(..., 3000) | 定闹钟:3 秒后再来一次 |
效果:程序每 3 秒打印一次 "Hello World!",并且每次都是"借 → 用 → 还"的完整循环——内存永远不会泄漏。
五、调试仿真:跑起来看效果
第一步:下载并进入仿真
使用仿真器把 ZigBee 开发板连接到电脑,单击 Download and Debug(Ctrl+D)按钮进行程序的编译、链接和下载,并进入仿真模式。
第二步:运行程序
进入仿真模式之后,单击 Go(F5)按钮运行程序。
第三步:观察输出
程序运行后,会每隔 3 秒在 Terminal I/O 窗口中输出一次 "Hello World!":

看到连续刷屏的 "Hello World!",说明:定时器事件在周期触发 → 动态内存申请/拷贝/释放正常 → 整个链路跑通了!
六、小结
| 问题 | 答案 |
|---|---|
| 什么是动态内存? | 程序运行时按需申请、用完释放的内存 |
| 申请 API? | osal_mem_alloc(size) |
| 释放 API? | osal_mem_free(ptr) —— 必须调用! |
| 内存操作 API? | osal_memcpy(复制)/ osal_memset(清零/填充) |
| 不释放的后果? | 内存泄漏:可用内存越来越少,程序最终崩溃 |
| 怎么让事件周期执行? | 处理完事件再 osal_start_timerEx 触发自己 |
一句话总结:动态内存 = "借(alloc)→ 用(cpy/set)→ 还(free)"三段式,配合定时器事件就能做出"每 3 秒报一次时"的周期任务。记住铁律:借了必须还,不还就泄漏。
内容来源:紫甲 IoT《ZigBee 3.0 开发指南》第三部分·3.4 节,经本站整理优化以便于理解;原站链接