笔记2:C语言的内存管理,用户栈的代码实现
概要
本节是笔记2:C语言的内存管理,用户栈的代码实现学习记录。
内存分区
C语言中的内存分区从上往下依次是:
- 栈区:存放局部变量、函数传递的参数、函数返回值及返回地址、const定义的局部变量。
- 堆区:一块可分配内存,堆区由程序员分配内存和释放
- .bss段:存放未初始化或初始值为0的全局变量,特点是
不占用可执行文件空间,.bss段通常需要进行清零操作。 - .data段:存放有初始值的全局变量、静态变量,其中const修饰的变量存放在.rodata段。
.data段涉及代码重定位,.data的初始值需要被拷贝至运行地址才能正常访问,在C标准函数__main中实现拷贝。 - 常量区:const修饰的全局变量或静态变量,STM32中会将其保存在FLASH中,也叫
.rodata段。 - 代码段:存放程序的代码,即被转化为机器码的二进制数据。
- 空字节段:从0地址开始直到代码段的区域, 是没有用到的字节区域,
一般存放指向NULL的空指针。
堆
malloc
一般使用方式如下:
1 | |
申请一块想要的内存大小并返回首地址,Linux中内部是通过sbrk函数向内核申请内存来实现的,其内部还需要实现查找内存块、各种内存块链表操作等,这里不细说。
free
一般使用方式如下:
1 | |
free只能释放malloc返回的指针,否则会报错,free内部操作较为复杂,除了将已申请的malloc内存块插入到空闲内存块链表以外,还要完成相邻内存块合并等操作来最大限度利用空间,最大程度减少内存碎片。
==只malloc而不进行free将会导致内存泄漏,导致之前被申请的内存块处于空闲但不能被再次申请==
栈
栈操作通常由编译器自己完成,通常在程序需要调用函数跳转时进行栈的操作,栈操作有push入栈和pop出栈。可以通过编写简单的函数调用程序,然后观察其反汇编代码查看编译器入栈和出栈的过程。这里不细说,本节重点在下文。
用户栈的代码实现
用户栈分类
- 数组栈:栈内存是数组,一块连续的内存,大小自定义指定,数组的索引作为栈顶指针。
- 链表栈:栈内存需要动态分配,一个数据对应一个结点,大小视程序而定,不需要自己指定,以头结点的
node属性作为索引。
1 | |
新建栈和初始化栈
- 数组栈:定义一个栈结构体,编译器自动分配内存,再把==栈顶指针==即数组索引值设置为
0即可。 - 链表栈:定义一个栈结构体,编译器自动分配内存,再把==栈顶指针==即栈链表头部
next设置为NULL即可。
1 | |
Push实现
- 数组栈Push:先填入数据,再栈顶指针偏移。
- 链表栈Push:先申请一个新的数据结点的内存并将数据填入,再插入到栈链表头部结点
next属性之后。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36/********************************************************
* Description : 入栈
* @pStack_User : 目标栈的指针
* @data : 入栈数据
* Return : 0:入栈成功
* -1:入栈失败,可能是栈满了
**********************************************************/
int Push(struct stack_t *pStack_User, int data)
{
#if STACK_TYPE == ARRAY_STACK
/* 如果栈还没满 */
if(pStack_User->stack_sp != STACK_MAX_SIZE){
pStack_User->stack_data[pStack_User->stack_sp] = data; //数据入栈
pStack_User->stack_sp++; //栈顶指针偏移
return 0;
}
else{
/* 栈已满 */
return -1;
}
#elif STACK_TYPE == LIST_STACK
struct stack_t *p = (struct stack_t *)malloc(sizeof(struct stack_t));
if(p){
/* 先储存数据 */
p->data = data;
/* 再插入链表 */
p->next = pStack_User->next;
pStack_User->next = p;
return 0;
}
else{
/* 内存申请失败,入栈失败 */
return -1;
}
#endif
}
Pop实现
- 数组栈Pop:先将栈顶指针偏移,再返回数据。
- 链表栈Pop:先将当前栈顶指针
next属性处数据储存,再删除该结点并释放内存,最后返回数据。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35/********************************************************
* Description : 出栈
* @pStack_User : 目标栈的指针
* @data : 数据存储指针
* Return : 0:入栈成功
* -1:入栈失败,可能是栈满了
**********************************************************/
int Pop(struct stack_t *pStack_User, int *data)
{
#if STACK_TYPE == ARRAY_STACK
/* 如果栈不是空的 */
if(pStack_User->stack_sp != 0){
pStack_User->stack_sp--; //栈顶指针偏移
*data = pStack_User->stack_data[pStack_User->stack_sp]; //数据出栈
return 0;
}
else{
/* 栈是空的 */
return -1;
}
#elif STACK_TYPE == LIST_STACK
struct stack_t *p = pStack_User->next; //获得头结点后第一个栈结点指针
/* 如果栈不是空的 */
if(p){
pStack_User->next = p->next;//删除该栈结点
*data = p->data; //返回数据
free(p); //记得释放内存
return 0;
}
else{
/* 栈是空的 */
return -1;
}
#endif
}
用户栈操作注意
Push与Pop都需要判断栈满与栈空的情况,代码中均已体现。
完整工程代码
- 所有代码均已进行测试,在windows上可以正常运行。
1 | |
笔记2:C语言的内存管理,用户栈的代码实现
http://example.com/2024/08/30/笔记2:C语言的内存管理,用户栈的代码实现/