内存分配
C语言内存布局
程序执行的时候一定会占用内存空间。为了能够更好地管理内存空间,我们通常要对内存进行布局,将其划分为不同的功能块。这有利于提高执行的效率、提高空间利用率、提高代码的安全性等。
在PC中,操作系统会在RAM中开辟一段连续的内存空间供程序使用。我们把内存空间从低地址位到高地址位,划分为五大段,如下图所示。
Text(RO): code and constant data
- 代码段存放汇编产生的机器代码。
- 为了防止数据溢出导致覆盖代码,将Text放在最低地址处。
- 代码段是可以共享的,数据段是私有的,当运行多个程序的副本时,只需要保存一份代码段部分。这在现代的操作系统里面占据了极为重要的地位,特别是在有动态链接的系统中,可以节省大量的内存。
Data(RW): initialized global and static variables
Data段在可执行文件中,由系统从可执行文件中加载。
BSS(RW): uninitialized global and static variables
BSS段中的变量都会被初始化为确定的值0,这些初始化的0不需要存储在可执行文件中,而只需要在ELF文件头部的Section Table中说明BSS空间大小,在Symbol Table中说明符号即可。当文件加载运行时,才分配空间以及初始化。因此,BSS段不占用任何的磁盘空间。
Stack(RW): local variables
- 在程序运行时由编译器自动分配。
- 用来存储函数调用时的临时信息的结构,如函数调用所传递的参数、函数的返回地址、函数的局部变量等。
- 只是分配空间,并未对其初始化。因此局部变量必须要手动初始化。
- 连续的地址。
- 向低地址增长。这样栈空间的起始位置就能确定下来,动态的调整栈空间大小也不需要移动栈内的数据。
Heap(RW): dynamic memory
- 在程序运行时由程序员自主分配。
- 不连续的地址。因为其是使用链表来分配的。
- 向高地址增长。这样做内存管理相对要简单些。
嵌入式C语言内存布局
嵌入式的内存是很宝贵的。我们不可能像PC那样将所有的段全部放在RAM中。而应根据数据的特点,将只读的部分存入ROM中,将读写的部分存入RAM中。
Keil-MDK在编译后,会将程序分为四个部分。
- Code: Code in Text
- RO-data: Constant data in Text
- RW-data: Data
- ZI-data: BSS + Heap + Stack
将这四个部分分别存入RAM和ROM中。
- ROM: Code + RO-data + RW-data
- RAM: RW-data + ZI-data
ROM中还要存RW,因为掉电后RAM中所有数据都丢失了,每次上电RAM中的数据是被重新赋值的,而这些固定的初始值就是存储在ROM中的。
ROM中的指令至少应该有这样的功能:
- 将RW从ROM中搬到RAM中,因为RW是变量,变量不能存在ROM中。
- 将ZI所在的RAM区域全部清零,因为ZI区域并不在Image中,所以需要程序根据编译器给出的ZI地址及大小来将相应得RAM区域清零。ZI中也是变量,同理:变量不能存在ROM中。