C语言内存分配

C语言内存布局

程序执行的时候一定会占用内存空间。为了能够更好地管理内存空间,我们通常要对内存进行布局,将其划分为不同的功能块。这有利于提高执行的效率、提高空间利用率、提高代码的安全性等。

在PC中,操作系统会在RAM中开辟一段连续的内存空间供程序使用。我们把内存空间从低地址位到高地址位,划分为五大段,如下图所示。

img

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段不占用任何的磁盘空间。

img

Heap(RW): dynamic memory

  • 在程序运行时由编译器自动分配。
  • 用来存储函数调用时的临时信息的结构,如函数调用所传递的参数、函数的返回地址、函数的局部变量等。
  • 只是分配空间,并未对其初始化。因此局部变量必须要手动初始化。
  • 连续的地址。
  • 向低地址增长。这样栈空间的起始位置就能确定下来,动态的调整栈空间大小也不需要移动栈内的数据。

Stack(RW): local variables

  • 在程序运行时由程序员自主分配。
  • 不连续的地址。因为其是使用链表来分配的。
  • 向高地址增长。这样做内存管理相对要简单些。

嵌入式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中。

keil-MDK实际测试结果

char* 和 char[]

char* s = "string";  //定义了一个指针,指向了字符串常量。存在Text段中。会有warning,因为指针类型是可以改变的,而常量是不能改变的。
const char*s = "string";  //定义了一个常量指针,指向了字符串常量。存在Text段中。
char s[] = "string";  //定义了一个数组,数组里存储着字符串。存在Data段中。