概要
本人正在准备24年秋招,现记录从现在开始的复习笔记。
本节是笔记2:C语言的内存管理,用户栈的代码实现学习记录。
内存分区
C语言中的内存分区从上往下依次是:
- 栈区:存放局部变量、函数传递的参数、函数返回值及返回地址、const定义的局部变量。
- 堆区:一块可分配内存,堆区由程序员分配内存和释放
- .bss段:存放未初始化或初始值为0的全局变量,特点是
不占用可执行文件空间
,.bss段通常需要进行清零操作。 - .data段:存放有初始值的全局变量、静态变量,其中const修饰的变量存放在.rodata段。
.data段涉及代码重定位,.data的初始值需要被拷贝至运行地址才能正常访问,在C标准函数__main中实现拷贝。
- 常量区:const修饰的全局变量或静态变量,STM32中会将其保存在FLASH中,也叫
.rodata
段。 - 代码段:存放程序的代码,即被转化为机器码的二进制数据。
- 空字节段:从0地址开始直到代码段的区域, 是没有用到的字节区域,
一般存放指向NULL的空指针
。
堆
malloc
一般使用方式如下:
struct stack_t *p = (struct stack_t *)malloc(sizeof(struct stack_t));
申请一块想要的内存大小并返回首地址,Linux中内部是通过sbrk
函数向内核申请内存来实现的,其内部还需要实现查找内存块、各种内存块链表操作等,这里不细说。
free
一般使用方式如下:
free(p); //记得释放内存
free
只能释放malloc
返回的指针,否则会报错,free
内部操作较为复杂,除了将已申请的malloc
内存块插入到空闲内存块链表以外,还要完成相邻内存块合并等操作来最大限度利用空间,最大程度减少内存碎片。
只malloc
而不进行free
将会导致内存泄漏,导致之前被申请的内存块处于空闲但不能被再次申请
栈
栈操作通常由编译器自己完成,通常在程序需要调用函数跳转时进行栈的操作,栈操作有push
入栈和pop
出栈。可以通过编写简单的函数调用程序,然后观察其反汇编代码查看编译器入栈和出栈的过程。这里不细说,本节重点在下文。
用户栈的代码实现
用户栈分类
- 数组栈:栈内存是数组,一块连续的内存,大小自定义指定,数组的索引作为栈顶指针。
- 链表栈:栈内存需要动态分配,一个数据对应一个结点,大小视程序而定,不需要自己指定,以头结点的
node
属性作为索引。
#define STACK_MAX_SIZE 1*1024 //设置栈大小
#define ARRAY_STACK 1 //指定使用数组栈
#define LIST_STACK 2 //指定使用链表栈
#define STACK_TYPE ARRAY_STACK //指定使用哪种栈类型
//构建自定义栈结构体
typedef struct stack_t{
#if STACK_TYPE == ARRAY_STACK
int stack_data[STACK_MAX_SIZE]; //栈内存
int stack_sp; //栈顶指针
#elif STACK_TYPE == LIST_STACK
int data; //栈结点数据
struct stack_t *next; //指向下一个结点
#endif
}stacks;
新建栈和初始化栈
- 数组栈:定义一个栈结构体,编译器自动分配内存,再把栈顶指针即数组索引值设置为
0
即可。 - 链表栈:定义一个栈结构体,编译器自动分配内存,再把栈顶指针即栈链表头部
next
设置为NULL
即可。
void StackUserInit(struct stack_t *pStack_User)
{
#if STACK_TYPE == ARRAY_STACK
pStack_User->stack_sp = 0;
#elif STACK_TYPE == LIST_STACK
pStack_User->next = NULL;
#endif
}
Push实现
- 数组栈Push:先填入数据,再栈顶指针偏移。
- 链表栈Push:先申请一个新的数据结点的内存并将数据填入,再插入到栈链表头部结点
next
属性之后。
/********************************************************
* 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
属性处数据储存,再删除该结点并释放内存,最后返回数据。
/********************************************************
* 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上可以正常运行。
#include "stdio.h"
#include "stdlib.h"
#define STACK_MAX_SIZE 1*1024 //设置栈大小
#define ARRAY_STACK 1 //指定使用数组栈
#define LIST_STACK 2 //指定使用链表栈
#define STACK_TYPE ARRAY_STACK //指定使用哪种栈类型
//构建自定义栈结构体
typedef struct stack_t{
#if STACK_TYPE == ARRAY_STACK
int stack_data[STACK_MAX_SIZE]; //栈内存
int stack_sp; //栈顶指针
#elif STACK_TYPE == LIST_STACK
int data; //栈结点数据
struct stack_t *next; //指向下一个结点
#endif
}stacks;
/********************************************************
* 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
}
/********************************************************
* 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
}
void StackUserInit(struct stack_t *pStack_User)
{
#if STACK_TYPE == ARRAY_STACK
pStack_User->stack_sp = 0;
#elif STACK_TYPE == LIST_STACK
pStack_User->next = NULL;
#endif
}
void PrintStack(struct stack_t *pStack_User)
{
int buf;
int i=0;
while(Pop(pStack_User, &buf) == 0)
{
printf("Stack_User_Pop top%d: %d\r\n",i++, buf);
}
}
int main(int argc, char *argv[])
{
struct stack_t Stack_User;
StackUserInit(&Stack_User);
Push(&Stack_User, 100);
Push(&Stack_User, 200);
Push(&Stack_User, 300);
Push(&Stack_User, 400);
Push(&Stack_User, 500);
PrintStack(&Stack_User);
printf("完成!\r\n");
}