STM32【7】堆栈和段的概念(1)

发布于:2025-03-27 ⋅ 阅读:(57) ⋅ 点赞:(0)

段的概念_重定位的引入

1. 全局变量和局部变量引入栈的概念

在这里插入图片描述
这里面有一个全局变量,一个局部变量

现在问2个问题:

- 1.全局变量保存在哪里?Flash还是RAM?

它的初始值肯定是保存在flash上,运行的时候,因为他是可读可写,所以运行时它肯定是在内存里

- 2.局部变量保存在哪里?

我们看看反汇编,就一清二楚了

在这里插入图片描述
C函数的第1个参数,放在寄存器R0里(这个是定死的,第一个参数在R0,第二个参数在R2.。。。。)

  1. tmp在栈里

  2. 用val也就是r0来设置栈里的内容
    在这里插入图片描述
    tmp变量的地址是多少? “sp+0”

可以看到局部变量的地址跟栈密切相关

参数个数小于4个的话都会用寄存器来传递,至于大于4个参数的话,就是其他的方式,具体没有研究过,不过肯定没有这个效率高。
在这里插入图片描述
可以得出结论:

  1. 局部变量的地址跟栈相关
  2. SP的初始值是多少?可以影响后续的局部变量的地址
  3. 函数的调用深度,也会影响到栈
  4. 调用者的局部变量有多、有少,这也会影响到栈

我们再看看全局变量:
在这里插入图片描述
在返回的文件里面就可以看到全局变量的地址,他的初始值保存在哪里?

我们用一个比较特殊的数值来看看,static int g_a = 0x12345678;

这个全局变量的地址,在代码中怎么体现?
在这里插入图片描述
下图中,红框里面的两个数值,哪一个是全局变量g_a的地址?
01_example07_example.jpg
去 0x800016c 处,读到一个数值0x20000000,这个数值就是g_a的地址

现在我们知道了:

  1. 局部变量的地址跟栈相关

  2. 全局变量的地址,对应的是内存(严格来说是RAM),但是它的地址是怎么确定的呢?

你链接程序时,需要指定“数据段”的起始地址:
在这里插入图片描述我们使用这种简单的办法来指定“可读可写的内存”的基地址,基地址是0x20000000

在我们这个简单的程序里只有一个全局变量g_a,所以g_a的地址是0x20000000

在工程设置里,我们只是指定了内存的大小,这块内存怎么使用?
在这里插入图片描述
一部分用来保存全局变量,一部分用来当做栈,还可能有一部分没有用到
选择内存哪里当做栈?随你高兴,只要不会覆盖全局变量的空间就可以。
在这里插入图片描述
比如我们可以这样设置:
在这里插入图片描述
栈往下增长,如果定义一个局部变量:char [1000000000]会发生什么事?
在这里插入图片描述
执行时会发生什么事情?
这个情况比较极端,但是可以看到:

1.我们无法限定栈的使用范围,如果你的程序写得不好,栈就会溢出

我们只能指定栈的TOP地址,无法限定栈往下增长的范围

2.这也就是我们为什么一般把栈的TOP地址设定在内存的最高位置的原因

尽可能让栈往下增长时,可以使用更多的空间。

二、堆是什么?

堆是什么?

  1. 堆是一块内存

  2. 这块内存一开始是空闲的,也就是没人使用

  3. 我们可以从这块内存里分配出一小块来使用

  4. 我们也可以在使用完后,把这一小块内存放回去以便再次分配

堆是空闲内存吗?不是,可以从里面划分出来使用,划分出来的这块被使用的内存,也属于堆

堆:程序员自己管理的内存,可以从中申请内存,可以回收内存

你申请的内存,你想用来做什么都可以。但是,全局变量的地址是在连接时就确定了,所以它不在堆上。

深入讲解

  1. FreeRTOS里定义了一个全局数组:
    在这里插入图片描述用数组的方式申请一大块内存,里面可以放任何的东西,注意不要溢出!
  2. 很多程序,是使用“空闲内存”
    比如:全局变量从0x20000000开始,你设置栈执行一块0x200的内存,剩下的内存,如果你可以管理起来,能从中分配内存,它就是堆

当然,对于堆,我们管理它的时候肯定是要事先确定它的起始地址、结束地址
你从堆里划分空间时,从头部开始划分,或者从尾部开始划分,没有什么不同。

看看下面的代码:

int heap_start = 0x20004000;
int heap_end   = 0x20005000;

void *malloc(int len)
{
   void *ret = heap_start;
   heap_start += len;
   return ret;
}

这段代码分配得到的内存,能够释放吗?

释放:就是让这块内存能再次使用,再次malloc

假设第1次:buf1 = malloc(10),
在这里插入图片描述
假设第2次:buf1 = malloc(20);
在这里插入图片描述
现在想去释放buf1,你能写出一个 free 函数吗?void free(void *buf);

写不出,为什么?

比如:free(buf1); 我没办法根据buf1这个参数知道buf1有多大

就是说不知道回收、释放多大的空间

从上面的图里,知道buf1的大小吗?

可能会告诉我:buf2 - buf1,就是大小

但是:

void a()
{
 char *buf1 = malloc(10);

 b();

 free(buf1); // 根本无法访问另一个函数的局部变量buf2
}

void b()
{
 char *buf2 = malloc(20);
}

在我们这个场景里面,你没有办法知道buf2的地址,就没办法通过buf2-buf1确定buf1的长度

在实际应用中,你甚至不知道第2个malloc的地址是存在哪个变量里

所以,我们这个简单的malloc函数没有对应的free函数

我们使用的这个管理方法是有问题的,它可以分配空间,但是无法回收空间。

我们看看官方的malloc函数时怎么做的
我们看看上面的malloc方法

  1. buf1 = malloc(10);
  2. 分配的空间大小=10+头部
  3. 在头部里记录大小

多了个记录内存使用信息的头部用来管理
这种方法巧妙地把分配的内存的长度,记录了下来以后 free(buf1)时,怎么得到buf1的长度?
从头部读取buf1的长度记录
keil自带的malloc、free,大概就是这种方法
当然,内部实现会复杂得多

三. 段的概念

代码段、只读数据段、可读可写的数据段、BSS段。

char g_Char = 'A';           // 可读可写,不能放在ROM上,应该放在RAM里
const char g_Char2 = 'B';    // 只读变量,可以放在ROM上
int g_A = 0;   // 初始值为0,干嘛浪费空间保存在ROM上?没必要
int g_B;       // 没有初始化,干嘛浪费空间保存在ROM上?没必要

所以,程序分为这几个段:

  • 代码段(RO-CODE):就是程序本身,不会被修改
  • 可读可写的数据段(RW-DATA):有初始值的全局变量、静态变量,需要从ROM上复制到内存
  • 只读的数据段(RO-DATA):可以放在ROM上,不需要复制到内存
  • BSS段或ZI段:
    • 初始值为0的全局变量或静态变量,没必要放在ROM上,使用之前清零就可以
    • 未初始化的全局变量或静态变量,没必要放在ROM上,使用之前清零就可以
  • 局部变量:保存在栈中,运行时生成
  • 堆:一块空闲空间,使用malloc函数来管理它,malloc函数可以自己写,当然在fresros用的是数组的分配方式。

在这里插入图片描述
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到