RT_Thread应用18—内存管理2

ぐ巨炮叔叔 提交于 2019-12-28 01:37:54

第二十四章 内存管理(第二部分)

六、静态内存管理的函数接口

静态内存管理函数的使用——开发流程:

  1. 规划一片内存区域作为静态内存池。
  2. 调用 rt_mp_create()函数。进行静态内存使用前的创建。
  3. 调用 rt_mp_alloc()函数。系统内部将会从空闲链表中获取第一个空闲块,并返回该块的用户空间地址。
  4. 调用 rt_mp_free()函数。将该块内存加入空闲块链表,进行内存的释放。
    在这里插入图片描述
    对内存池的操作包含:创建 / 初始化内存池、申请内存块、释放内存块、删除 / 脱离内存池,但不是所有的内存池都会被删除,这与设计者的需求相关,但是使用完的内存块都应该被释放。

1、静态内存控制块

1 struct rt_mempool { 
 2     struct rt_object parent;     /**<继承自 rt_object */    (1) 
 **静态内存会在自身结构体里面包含一个对象类型的成员,通过这
个成员可以将内存挂到系统对象容器里面。 **
 3  
  4     void            *start_address;  /**< 内存池起始地址 */  (2)**内存池开始地址** 
 5     rt_size_t        size;       /**< 内存池大小 */    (3) **内存池大小**
 6  
 7     rt_size_t        block_size; /**< 内存块大小 */    (4)**内存块大小**   
 8     rt_uint8_t      *block_list;  /**< 内存块链表 */    (5) **内存块链表,所有可用的内存块都挂载在此链表上**
 9  
10     rt_size_t        block_total_count; /**< 内存块总数量 */  (6)**内存池数据区域中能够容纳的最大内存块数** 
11     rt_size_t        block_free_count; /**< 空闲内存块数量 */  (7) **内存池中空闲的内存块数**
12  
13     rt_list_t        suspend_thread;         (8) **挂起在内存池的线程列表**
14     rt_size_t        suspend_thread_count;       (9) **挂起在内存池的线程数量**
15 }; 
16 typedef struct rt_mempool *rt_mp_t; 

2 静态内存创建函数 rt_mp_create()

1 /** 
 2  * 此函数将创建一个 mempool 对象并从堆中分配内存池。 
 3  *  
 4  * 
 5  * @param name 内存池名称   
 6  * @param block_count 内存块数量 
 7  * @param block_size 内存块大小 
 8  * 
 9  * @return 已创建的内存池对象 
10  */ 
11 rt_mp_t rt_mp_create(const char *name,      (1) **name 内存池名称。**
12                      rt_size_t   block_count,    (2) **block_count 初始化内存池中可分配内存块最大数量**
13                      rt_size_t   block_size)    (3) **block_size 初始化内存块的大小**
14 {   
15     rt_uint8_t *block_ptr; 
16     struct rt_mempool *mp; 
17     register rt_base_t offset; 
18  
19     RT_DEBUG_NOT_IN_INTERRUPT; 
20  
21     /* 分配对象 */ 
22     mp = (struct rt_mempool *)rt_object_allocate(RT_Object_Class_MemPool, name); 
23     /* 分配对象失败 */ 
24     if (mp == RT_NULL)         (4)**分配内存池对象**     
25         return RT_NULL; 
26  
27     /* 初始化内存池信息 */ 
28     block_size     = RT_ALIGN(block_size, RT_ALIGN_SIZE); 
 (5) **初始化内存池信息,初始化内存块大小,使其对齐方式与系统内存对齐方式一致**
29     mp->block_size = block_size;        (6) **内存块大小按传递进来的 block_size 来进行初始化配置**
30     mp->size  = (block_size + sizeof(rt_uint8_t *)) * block_count; (7)
**计 算 得 出 内 存 池 需 要 的 内 存 大 小 , 其 大 小 为 (block_size  + 
sizeof(rt_uint8_t  *))  *  block_count,也就是[内存块大小  +4 个字节大小(指向内存池控制
块)]乘以内存块的数量。 ** 
31  
32     /* 分配内存 */ 
33     mp->start_address = rt_malloc((block_size + sizeof(rt_uint8_t *)) * 
34                                   block_count);     (8) **分配内存池**
35     if (mp->start_address == RT_NULL) { 
36         /* 没有足够内存,删除内存池对象句柄 */ 
37         rt_object_delete(&(mp->parent));      (9) 
**系统已经没有足够的内存了,分配失败,需要删除内存池对象句柄,所以在静态内存池创建的时候一定要考虑到系统的内存大小。**
38  
39         return RT_NULL; 
40     } 
41  
42     mp->block_total_count = block_count;      (10) **分配成功,静态内存控制块的 block_total_count(内存块总数量)**
43     mp->block_free_count  = mp->block_total_count;    (11)**初始化空闲内存块数量。** 
44  
45     /* 初始化阻塞链表 */ 
46     rt_list_init(&(mp->suspend_thread));      (12) **初始化线程的阻塞列表和在此列表上线程的数量**
47     mp->suspend_thread_count = 0; 
48  
49     /* 初始化空闲内存块链表 */ 
50     block_ptr = (rt_uint8_t *)mp->start_address;    (13) **初始化第一个内存块的起始地址。 **
51     for (offset = 0; offset < mp->block_total_count; offset ++) { (14) 
**:在 for 循环中初始化空闲内存块列表,循环执行次数为空闲内存块的数量值。**
52         *(rt_uint8_t **)(block_ptr + offset * (block_size + sizeof(rt_uint8_t *))) 
53          = block_ptr + (offset + 1) * (block_size + sizeof(rt_uint8_t *)); 
54     }               (15)**将所有的内存块都连接起来,在分配的时候更容易管理**   
55  
56     *(rt_uint8_t **)(block_ptr + (offset - 1) * (block_size + sizeof(rt_uint8_t *))) 
57         = RT_NULL;            (16)**最后一块内存块的下一个内存是没有了,就是 NULL** 
58  
59     mp->block_list = block_ptr;        (17)**内存块列表指向第一块可用内存块** 
60  
61     return mp;            (18) **创建成功返回内存池对象句柄**
62 } 
63 RTM_EXPORT(rt_mp_create); 

** (15)将所有的内存块都连接起来,在分配的时候更容易管理 **
在这里插入图片描述
使用该函数接口可以创建一个静态内存池,前提是在系统资源允许的情况下(最主要
的是动态堆内存资源)才能创建成功。
创建内存池成功将返回内存池的句柄,否则返回 RT_NULL。
例子:

1 /* 定义内存池控制块 */ 
 2 static rt_mp_t test_mp = RT_NULL; 
 3 /* 定义申请内存的指针 */ 
 4 static rt_uint32_t *p_test = RT_NULL; 
 5 /* 相关宏定义 */ 
 6 #define  BLOCK_COUNT   20    //内存块数量 
 7 #define  BLOCK_SIZE   3      //内存块大小 
 8 /* 创建一个静态内存池 */ 
 9 test_mp = rt_mp_create("test_mp", 
10                        BLOCK_COUNT, 
11                        BLOCK_SIZE); 
12 if (test_mp != RT_NULL) 
13     rt_kprintf("静态内存池创建成功!\n\n"); 

3 静态内存删除函数 rt_mp_delete()

删除内存池时,会首先唤醒等待在该内存池对象上的所有线程(返回-RT_ERROR),然后再释放已从内存堆上分配的内存池数据存放区域,然后删除内存池对象。删除内存池后将无法向内存池申请内存块

1 /** 
 2  * 这个函数会删除内存池对象并且释放内存池对象的内存 
 3  * 
 4  * @param mp 内存池对象句柄 
 5  * 
 6  * @return 删除成功返回 RT_EOK 
 7  */ 
 8 rt_err_t rt_mp_delete(rt_mp_t mp)      (1) **:mp  内存池对象句柄**
 9 { 
10     struct rt_thread *thread; 
11     register rt_ubase_t temp; 
12  
13     RT_DEBUG_NOT_IN_INTERRUPT; 
14  
15     /* 检查内存池对象 */ 
16     RT_ASSERT(mp != RT_NULL);        (2) **检查内存池对象句柄 mp 是否有效**
17  
18     /* 唤醒所有在阻塞中的线程 */ 
19     while (!rt_list_isempty(&(mp->suspend_thread))) {  (3) **如果当前有线程挂在内存池的阻塞列表中,需要将该线程唤醒,
直到没有线程阻塞的时候才退出 while 循环。 **
20         /* 关中断 */ 
21         temp = rt_hw_interrupt_disable();     
22  
23         /* 获取阻塞线程 */        (4) **获取阻塞的线程**
24         thread = rt_list_entry(mp->suspend_thread.next, struct rt_thread, tlist); 
25         /* 返回线程错误 */   
26         thread->error = -RT_ERROR;         
27  
28         /* 
29          * 恢复线程 
30          * 在 rt_thread_resume 函数中,它将从挂起列表中删除当前线程 
31          *  
32          */ 
33         rt_thread_resume(thread);      (5) 
**调用 rt_thread_resume 线程恢复函数,将该线程恢复,该函数会将线程从阻塞链表中删除。 **
34  
35         /* 挂起线程数减一 */ 
36         mp->suspend_thread_count --;      (6) **将内存池控制块中记录线程挂起数量的 suspend_thread_count 变量
减一。**
37    
38         /* 开中断 */ 
39         rt_hw_interrupt_enable(temp);   
40     } 
41  
42 #if defined(RT_USING_MODULE) && defined(RT_USING_SLAB)  (7)
**在这里我们并没有使用 slab 分配机制,未使能 RT_USING_SLAB
这个宏定义,所以还不需要使用 rt_module_free 释放内存函数。 ** 
43      
44     if (mp->parent.flag & RT_OBJECT_FLAG_MODULE) 
45         rt_module_free(mp->parent.module_id, mp->start_address); 
46     else 
47 #endif 
48  
49         /* 释放申请的内存池 */ 
50         rt_free(mp->start_address);      (8) 
**释放内存池的内存,因为这个内存池是从系统堆内存动态划分的,删除后要进行释放。 **
51  
52     /* 删除内存池对象 */ 
53     rt_object_delete(&(mp->parent));      (9) **调用 rt_object_delete()函数删除内存池对象**
54  
55     return RT_EOK;          (10) **返回删除结果 RT_EOK。 **
56 } 
57 RTM_EXPORT(rt_mp_delete); 

注意:删除的时候会将所有因为申请不到内存块而进入阻塞的线程恢复,被恢复的线程会得到一个-RT_ERROR,所以,建议在删除内存池之前我们应确保所有的线程没有阻塞,并且以后也不会再向这个内存池申请内存块,才进行删除操作。
例子:

1 /* 定义内存池控制块 */ 
 2 static rt_mp_t test_mp = RT_NULL; 
 3  
 4 rt_err_t uwRet = RT_EOK; 
 5  
 6 /* 删除一个静态内存池 */ 
 7 uwRet = rt_mp_delete(test_mp); 
 8 if (RT_EOK == uwRet) 
 9     rt_kprintf("静态内存池删除成功!\n\n"); 

4 静态内存初始化函数 rt_mp_init()

与创建内存池不同的是:此处内存池对象所使用的内存空间是由用户指定的一个缓冲区空间用户把缓冲区的指针传递给内存池对象控制块,其余的初始化工作与创建内存池相同

1 /** 
 2  *  
 3  * 此函数将初始化内存池对象,通常用于静态对象。 
 4  * 
 5  * @param mp 内存池对象 
 6  * @param name 内存池名称 
 7  * @param start 内存池起始地址 
 8  * @param size 内存池总大小 
 9  * @param block_size 每个内存块的大小 
10  * 
11  * @return RT_EOK 
12  */ 
13 rt_err_t rt_mp_init(struct rt_mempool *mp,      (1)**mp 内存池对象句柄** 
14                     const char        *name,      (2)**name 内存池名称,是字符串常量类型。 **   
15                     void              *start,      (3) **start 内存池起始地址,由用户自己定义的具体的起始地址**
16                     rt_size_t          size,      (4)**size 初始化内存池总容量大小** 
17                     rt_size_t          block_size)    (5)  **block_size 每个内存块的大小。** 
18 { 
19     rt_uint8_t *block_ptr; 
20     register rt_base_t offset; 
21  
22     /* 检查内存池 */ 
23     RT_ASSERT(mp != RT_NULL);          (6)**检查内存池对象句柄 mp 是否有效** 
24  
25     /* 初始化内存池对象 */ 
26     rt_object_init(&(mp->parent), RT_Object_Class_MemPool, name); (7) **初始化内存池内核对象**
27  
28     /* 初始化内存池 */ 
29     mp->start_address = start;         (8)**初始化内存池,内存池的地址是由用户传递进来的地址。** 
30     mp->size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);    (9) 
**初始化内存池容量 size,使其以 4 字节对齐方式对齐,如果不满足
对齐倍数将返回其最小的对齐倍数,如想要对齐 13 字节大小的内存块,  RT_ALIGN
(13,4) ,将返回 16(字节)。 **  
31  
32     /* 内存块大小对齐 */ 
33     block_size = RT_ALIGN(block_size, RT_ALIGN_SIZE);    (10)**初始化内存块大小 block_size,** 
34     mp->block_size = block_size; 
35  
36      
37     mp->block_total_count = mp->size / (mp->block_size + sizeof(rt_uint8_t *)); 
38     mp->block_free_count  = mp->block_total_count;    (11) 
**通过计算得出内存池中最大内存块数量,例如内存池大小为 200
个字节,内存块的大小为 16 个字节,但是需要再加上 4 个字节大小的内存头(指向内存池
控制),很显然,内存块的数量最大为 5=200/(16+4),并且初始化可用空闲内存块个数。 **
39  
40     /* 初始化阻塞链表 */ 
41     rt_list_init(&(mp->suspend_thread));      (12) **初始化线程的阻塞列表和线程阻塞的数量**
42     mp->suspend_thread_count = 0;         
43  
44     /* 初始化内存块空闲链表 */ 
45     block_ptr = (rt_uint8_t *)mp->start_address;    (13) **初始化第一个内存块的起始地址。 **
46     for (offset = 0; offset < mp->block_total_count; offset ++) {(14) 
**for 循环中初始化空闲内存块链表,循环执行次数为空闲内存块的数量值。 **
47      *(rt_uint8_t **)(block_ptr + offset * (block_size + sizeof(rt_uint8_t *))) = 
48      (rt_uint8_t *)(block_ptr + (offset + 1) * (block_size + sizeof(rt_uint8_t *))); 
49     }               (15) **将所有的空闲内存块都连接起来,在分配的时候更容易管理**
50  
51  *(rt_uint8_t **)(block_ptr + (offset - 1) * (block_size + sizeof(rt_uint8_t *))) =
52         RT_NULL;            (16)**最后一块内存块的下一个内存是没有了,就是 NULL** 
53  
54     mp->block_list = block_ptr;        (17) **内存控制块的 block_list 内存块链表指向第一块可用内存块**
55  
56     return RT_EOK;            (18)**创建成功返回内存池对象句柄。** 
57 } 
58 RTM_EXPORT(rt_mp_init);  

(15):将所有的空闲内存块都连接起来,在分配的时候更容易管理
在这里插入图片描述
例子:

1 /* 定义内存池控制块 */ 
 2 static rt_mp_t test_mp = RT_NULL; 
 3 static rt_uint8_t mempool[4096]; 
 4  
 5 rt_err_t uwRet = RT_EOK; 
 6  
 7 /* 初始化内存池对象 */ 
 8 uwRet = rt_mp_init(&test_mp,     /**内存池对象**/ 
 9                    "test_mp",    /**内存池名称**/ 
10                    &mempool[0],   /**内存池起始地址**/ 
11                    sizeof(mempool),   /**内存池总大小**/ 
12                    80);    /**每个内存块的大小**/ 
13 if (RT_EOK == uwRet) 
14     rt_kprintf("初始化内存成功!\n"); 

5 静态内存申请函数 rt_mp_alloc()

用于申请固定大小的内存块。在这里插入图片描述

1 /** 
 2  * 这个函数用于从指定内存池分配内存块 
 3  * 
 4  * @param mp 内存池对象 
 5  * @param time 超时时间 
 6  * 
 7  * @return 分配成功的内存块地址或 RT_NULL 表示分配失败 
 8  */ 
 9 void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time)    (1) **mp 内存池对象,time 超时时间。**
10 { 
11     rt_uint8_t *block_ptr; 
12     register rt_base_t level; 
13     struct rt_thread *thread; 
14     rt_uint32_t before_sleep = 0; 
15  
16     /* 获取当前线程 */ 
17     thread = rt_thread_self();       (2) **获取当前线程。 **
18  
19     /* 关中断 */ 
20     level = rt_hw_interrupt_disable(); 
21  
22     while (mp->block_free_count == 0) {     (3) **如果无内存块可用,进入 while 循环。 **
23         /* 无内存块可用 */ 
24         if (time == 0) {        (4) **如果用户不设置等待时间,则直接返回错误码**
25             /* 开中断 */ 
26             rt_hw_interrupt_enable(level); 
27  
28             rt_set_errno(-RT_ETIMEOUT); 
29  
30             return RT_NULL; 
31         } 
32  
33         RT_DEBUG_NOT_IN_INTERRUPT; 
34  
35         thread->error = RT_EOK; 
36  
37         /* 需要挂起当前线程 */ 
38         rt_thread_suspend(thread);      (5) **因为能到这一步,用户肯定设置了等待时间的,那么,不管三七
二十一将当前线程挂起。**
39         rt_list_insert_after(&(mp->suspend_thread), &(thread->tlist)); 
40         mp->suspend_thread_count++;      (6) **记录因为挂起的线程数量。**
41  
42         if (time > 0) { 
43             /* 获取当前系统时间 */ 
44             before_sleep = rt_tick_get();    (7) **获取当前系统时间**
45  
46             /* 重置线程超时时间并且启动定时器 */ 
47             rt_timer_control(&(thread->thread_timer),  (8) **重置线程计时器的超时时间,调用 rt_timer_control()函数改变当前
线程阻塞时间 thread_timer。 **
48                              RT_TIMER_CTRL_SET_TIME, 
49                              &time); 
50             rt_timer_start(&(thread->thread_timer));  (9)**启动定时器开始计时。** 
51         } 
52  
53         /* 开中断 */ 
54         rt_hw_interrupt_enable(level); 
55  
56         /* 发起线程调度 */ 
57         rt_schedule();         (10)**因为现在线程是等待着了,要进行线程切换,
所以进行一次线程调度。** 
58  
59         if (thread->error != RT_EOK) 
60             return RT_NULL; 
61  
62         if (time > 0) { 
63             time -= rt_tick_get() - before_sleep; 
64             if (time < 0) 
65                 time = 0; 
66         } 
67         /* 关中断 */ 
68         level = rt_hw_interrupt_disable(); 
69     } 
70  
71     /* 内存块可用,记录当前可用内存块个数,申请之后空闲内存块数量减一 */ 
72     mp->block_free_count--;        (11) **当前内存池中还有内存块可用,记录当前可用内存块个数,申请
之后可用内存块数量减一。 **
73  
74     /* 获取内存块指针 */ 
75     block_ptr = mp->block_list;      (12)**获取内存块指针,指向空闲的内存块。** 
76     RT_ASSERT(block_ptr != RT_NULL); 
77  
78     /* 设置下一个空闲内存块为可用内存块 */ 
79     mp->block_list = *(rt_uint8_t **)block_ptr;   (13) **设置当前申请内存块的下一个内存块为可用内存块,将 mp-
>block_list 的指针指向下一个内存块**
80  
81      
82     *(rt_uint8_t **)block_ptr = (rt_uint8_t *)mp;  (14)   
83  
84     /* 开中断 */ 
85     rt_hw_interrupt_enable(level); 
86  
87     RT_OBJECT_HOOK_CALL(rt_mp_alloc_hook, 
88                         (mp, (rt_uint8_t *)(block_ptr + sizeof(rt_uint8_t 
*))));
89  
90     return (rt_uint8_t *)(block_ptr + sizeof(rt_uint8_t *));(15)
**返回用户真正能读写操作的内存地址,其地址向下偏移了 4 个字
节。 ** 
91 } 
92 RTM_EXPORT(rt_mp_alloc);  

(13):设置当前申请内存块的下一个内存块为可用内存块,将 mp->block_list 的指针指向下一个内存块,
在这里插入图片描述
(14):如图所示,每一个内存块的前 4 个字节是指向内存池控制块的指针,
目的:是让我们在释放内存的时候能找到内存控制块。
:为什么要记录内存控制块指针呢?
ANS:因为 block_list 是单链表,在申请成功内存的时候,已使用的内存块相当于脱离了内存块列表,那么在释放内存块的时候就没办法正常释放,所以需要保存内存控制块的指针。
例子:

1 /* 定义申请内存的指针 */ 
 2 static rt_uint32_t *p_test = RT_NULL; 
 3 rt_kprintf("正在向内存池申请内存...........\n"); 
 4  
 5 p_test = rt_mp_alloc(test_mp,0); 
 6 if (RT_NULL == p_test) /* 没有申请成功 */ 
 7     rt_kprintf("静态内存申请失败!\n"); 
 8 else 
 9     rt_kprintf("静态内存申请成功,地址为%d!\n\n",p_test); 
10  
11 rt_kprintf("正在向 p_test 写入数据...........\n"); 
12 *p_test = 1234; 
13 rt_kprintf("已经写入 p_test 地址的数据\n"); 
14 rt_kprintf("*p_test = %.4d ,地址为:%d \n\n", *p_test,p_test); 
15   

6 静态内存释放函数 rt_mp_free()

任何内存块使用完后都必须被释放,否则会造成内存泄露,导致系统发生致命错误。
在这里插入图片描述

1 /** 
 2  * 这个函数会释放一个内存块 
 3  * 
 4  * @param block 要释放的内存块的地址 
 5  */ 
 6 void rt_mp_free(void *block)          (1) **block 要释放的内存块的地址。 **
 7 { 
 8     rt_uint8_t **block_ptr; 
 9     struct rt_mempool *mp; 
10     struct rt_thread *thread; 
11     register rt_base_t level; 
12  
13     /* 获取块所属的池的控制块 */ 
14     block_ptr = (rt_uint8_t **)((rt_uint8_t *)block - sizeof(rt_uint8_t *));(2) 
**每个内存块中前 4 个字节保存的信息就是指向内存池控制块指针,
所以,需要进行指针的偏移,为了获得内存池控制块的地址。**
15     mp    = (struct rt_mempool *)*block_ptr;    (3) **获取内存块所属的内存池对象 mp。 **
16  
17     RT_OBJECT_HOOK_CALL(rt_mp_free_hook, (mp, block));   
18  
19     /* 关中断 t */ 在这里插入代码片
20     level = rt_hw_interrupt_disable(); 
21  
22     /* 增加可以的内存块数量 */ 
23     mp->block_free_count ++;        (4)**记录当前可用内存块数量。 ** 
24  
25     /* 将释放的内存块添加到 block_list 链表中 */ 
26     *block_ptr = mp->block_list;      (5) **将释放的内存块添加到 block_list 链表中**
27     mp->block_list = (rt_uint8_t *)block_ptr;    (6) 
28  
29     if (mp->suspend_thread_count > 0) {     (7) **如果当前有线程因为无法申请内存进入阻塞的话,会执行 while
循环中的代码。 **
30         /* 获取阻塞的线程 */ 
31         thread = rt_list_entry(mp->suspend_thread.next,  (8) **获取阻塞的线程。 **
32                                struct rt_thread, 
33                                tlist); 
34  
35         /* 重置线程错误为 RT_EOK */ 
36         thread->error = RT_EOK; 
37  
38         /* 恢复线程 */ 
39         rt_thread_resume(thread);      (9)**调用 rt_ipc_list_resume 函数将该线程恢复。 ** 
40  
41         /* 记录阻塞线程数量,减一 */   
42         mp->suspend_thread_count --;      (10) **记录阻塞线程数量,suspend_thread_count 减一。 **
43  
44         /* 开中断 */ 
45         rt_hw_interrupt_enable(level); 
46  
47         /* 发起线程调度 */ 
48         rt_schedule();         (11) **恢复挂起的线程,需要发起一次线程调度。 **
49  
50         return; 
51     } 
52  
53     /* 开中断 */ 
54     rt_hw_interrupt_enable(level);         
55 } 
56 RTM_EXPORT(rt_mp_free); 

(5):将释放的内存块添加到 block_list 链表中,内存控制块的指向当前
可用内存链表头

在这里插入图片描述
总结:
内存释放的使用是非常很简单的,仅将需要释放的内存块地址传递进去即可,系统会根据内存块前 4 字节的内容自动找到对应的内存池控制块,然后根据内存池控制块来进行释放内存操作。

1 /* 定义申请内存的指针 */ 
 2 static rt_uint32_t *p_test = RT_NULL; 
 3  
4 rt_kprintf("正在释放内存...........\n"); 
 5 rt_mp_free(p_test); 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!