堆栈,数据,文本,heap,bss,text data,stack
text data bss stack heap 段
根据APUE,程序分为下面的段:.text, data (initialized), bss, stack, heap。
data/bss/text:
text段在内存中被映射为只读,但.data和.bss是可写的。
bss是英文Block Started by Symbol的简称,通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序载入时由内核清0。BSS段属于静态内存分配。初始值也是由用户自己定义的连接定位文件所确定,用户应该将定义在可读写的RAM区内,源程序中使用malloc分配的内存就是这一块,不是根据data大小确定,主要由程序中同时分配内存最大值所确定,不过如果超出了范围,也就是分配失败,可以等空间释放之后再分配。
text段是程序代码段,在AT91库中是表示程序段的大小,是由编译器在编译连接时自动计算的,当你在链接定位文件中将该符号放置在代码段后,那么该符号表示的值就是代码段大小,编译连接时,该符号所代表的值会自动代入到源程序中。
data包含静态初始化的数据,所以有初值的全局变量和static变量在data区。段的起始位置也是由连接定位文件所确定,大小在编译连接时自动分配,与程序大小没有关系,但和程序使用到的全局变量,常量数量相关。
stack/heap:
栈(stack)保存函数的局部变量和参数。是一种“后进先出”(Last In First Out,LIFO)的数据结构,这意味着最后放到栈上的数据,将会是第一个从栈上移走的数据。对于哪些暂时存贮的信息,和不需要长时间保存的信息来说,LIFO这种数据结构非常理想。在调用函数或过程后,系统通常会清除栈上保存的局部变量、函数调用信息及其它的信息。栈另外一个重要的特征是,地址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低。栈(stack)的顶部在可读写的RAM区的最后。
堆(heap)保存函数内部动态分配内存,是另外一种用来保存程序信息的数据结构,更准确的说是保存程序的动态变量。堆是“先进先出”(First In first Out,FIFO)数据结构。只允许在堆的一端插入数据,在另一端移走数据。堆的地址空间“向上增加”,即当堆上保存的数据越多,堆的地址就越高。
在程序中的存储详情如下:
char
*string = "Hello World";
int iSize;
char *fun(void)
{
char *p;
iSize = 8;
p = malloc(iSize);
return p;
}
存放的位置
Text段:上面标记为红色的部分(还包括整个fun函数,由于下边需要,所以没有标记)都存放在Text段,Text段用来存放代码(二进制文件)和常量,该段的数据通常是只读的
例:
Text段:上面标记为红色的部分(还包括整个fun函数,由于下边需要,所以没有标记)都存放在Text段,Text段用来存放代码(二进制文件)和常量,该段的数据通常是只读的
例:
- char *p = "Hello";
- p[0] = 'a';
VC6.0下,编译无错,但运行是提示内存不可写,这就是因为"Hello"存放在Text段,是只读的,你不能去修改.
Data段: 已经初始化了的全局变量string就存放在该段,Data段用来存放初始化了的变量,即初始化了的全局变量和静态变量
BSS段: 未初始化的全局变量iSize存放在这,BSS段主要存放在未初始化的变量,即未初始化的全局变量和静态变量
Heap段: 程序员手动分配的malloc(iSize)这篇内存存放在该段,也需要用户自行释放
Stack段:函数里的局部变量*p存放在该段,Stack段主要存放局部变量、临时变量、函数相互调用的返回的地址,该段由编译器自行分配和释放
接下来来看看,什么被分配了内存,什么时候释放内存:
全局变量和静态变量:程序开始的时候就分配了,程序结束时候释放内存。
局部变量:函数调用的时候为变量分配内存,调用结束释放内存。
堆里的:调用malloc时分配内存,调用free时释放。
.text .data .bss .stack .heap 详解
.text 代码段:用来存放代码和常量(const 关键字定义的变量)。
.data 数据段:用来存放有初始值的全局变量、全部静态变量(static 关键字定义的变量)。注意全部静态变量包括全局静态变量和局部静态变量,并且不论这些变量是否有初始值。即不管有没有初始值,也不管是全局变量还是定义在函数内的局部变量,只要是用 static 关键字定义的变量,都放在 .data 数据段内。
.bss BSS段:用来存放没有初始值的全局变量。(没有初始值的局部变量好像也存在这里,网上验证帖子,待验证)。
.stack 栈区: 用来存放局部变量,函数的参数,返回值等,由编译器自动分配释放。如一个函数被调用后,产生的临时变量都会存到栈区的顶部,当函数完成后,会自动从顶部将刚使用的数据销毁。栈区的地址是从高地址向下增长的。
.heap 堆区: 用来动态内存分配,如 malloc, new 申请的内存,由程序员手动分配释放。程序中不释放,则程序结束时,由OS回收;据说这个和数据结构中的堆 没有什么关系;堆区使用时地址向上增长。
以上5段,在生成的 hex 文件中是怎么分配呢?
Hex 文件分为三部分(可通过 map 文件查看到)
.text 代码段
- .data 数据段
.bss, .stack, .heap的位置信息(即起始位置和大小)
程序运行时,这5段各起什么作用,又在物理存储空间哪个位置?
现在的计算机架构主要有两种:
- 冯诺依曼架构:必须从 RAM 中取出执行代码,即执行代码要先存入 RAM 中。
- 哈佛架构:可以直接从 flash 执行代码。
ARM 架构是哈佛架构,可以直接从 flash 取指令执行,所以在程序运行期间:
.text 段位于 flash 内,具体位置和 hex 文件中 .text 段的位置一致,程序运行时直接从 flash 内读取指令并执行。.text 段都是代码和常量,只读,所以可以放在 flash 内。
.data 段在MCU启动过程中,会被从 flash 内 copy 到 SRAM 内(各家的启动代码都会做此操作)。程序运行时是操作 SRAM 中的变量。 .data 段内都是变量,可读可写,所以会被放在 SRAM 内。所以 .data 段有两个地址,一个是在 flash 内的地址,flash 内放的就是变量的初始值;另一个是在 SRAM 内的地址,SRAM 内的地址就是程序运行时变量所在的位置。一个变量初始化的过程,就是启动代码将 .data 段从 flash 内 copy 到 SRAM 中的过程,因此 SRAM 中的 .data 区域,在程序运行时(MCU 启动代码运行之后)一开始就有值了。这也解释了,为什么 static 变量的初始化只执行一次(即在编译时初始化),注意这种通过启动代码 copy flash 中内容到对应 SRAM 中的所谓在编译时初始化,和函数内部局部变量是通过用户代码初始化是不一样的操作,局部变量通过用户代码初始化后面会详细讲解。另外要注意,.data 段内数据分有初始值的全局变量和 static 变量两块,这两块变量虽然都在 .data 段,但作用域不同,全局变量是全局作用域,而 static 变量是局部作用域。
.bss, .stack 和 .heap 段在 hex 文件内有对应的起始位置和大小信息,MCU 在启动时(启动代码)会根据这些信息配置 SRAM,给这三段在 SRAM 中按 hex 文件中的信息分配好对应的位置和大小。程序运行期间,对这三段的操作都是在 SRAM 中进行的。注意 .bss 要初始化,一般是启动代码会做这个操作,将整个 .bss 段初始化为 0 (可参考bss段为什么要初始化)。
总结一下这5段在物理存储器上的位置及占用大小:
Flash 占用大小 = .text 大小 + .data大小 + 其它section(如.bss, .stack, .heap等) 位置信息大小
SRAM 占用大小 = .data 大小 + .bss 大小 + .stack大小 + .heap大小
程序运行时,这 5 段在物理存储器上的位置如下图
下面用一段代码来详细介绍上面这5段
各变量所在位置如上图注释,下面把一些重点概念,如函数中的局部变量如何赋值,用此代码讲解一下:
在编译时初始化和函数内局部变量用户代码初始化
如下图
全局变量 a 和 静态变量 e 是在编译时初始化:这两个变量都在 .data 段,MCU 启动时,启动代码会将 .data 段从 flash copy 到 SRAM ,copy 操作完成后, SRAM .data 段中的变量就都已经有了初始值(之前存在 flash 中的初始值)。本例中的变量 a 和 c 就是在这个过程中完成的初始化。这个初始化过程中,不需要调用任何用户代码,所以被称作在编译时初始化。注意:编译时初始化只初始化一次。因为是启动代码执行 copy 操作完成的初始化,启动代码只执行一次,所以这个初始化的操作也就只会执行一次。
局部变量 g 在 .stack 中,也就是说,只有在程序运行时,并在调用 main 函数时,才会在 SRAM 的 stack 区域内给变量 g 开出一个空间,让程序可以对 g 进行操作。那如何对变量 g 赋初始值 2 的呢?是执行 g = 2; 这段代码,g =2; 这段代码在 .text 中(存在 flash 中),每次调用 fun 函数时都会调用 g = 2; 这段代码,这段代码会给在 stack 中的变量 g 赋初始值 2。但和编译时初始化不同,每次调用 fun 函数,都会重新对变量 g 做赋值操作,即函数被调用几次,函数内局部变量的初始化操作就会执行几次。所以用户代码初始化函数内局部变量会初始化多次。
函数内局部变量初始化和函数内静态变量初始化的区别
局部变量 g 的初始化上面已详细讲解。
局部静态变量 y 的初始化和 g 不同,静态变量 y 是在 .data 段中,和全局变量 a 一样,是用在编译时初始化的方法完成初始化的。详细过程参考上面关于变量 a 的初始化过程。
注意:这样 g 和 y 在初始化值方面的用法就完全不同了。在 fun 函数每次被调用时, g 都被初始化为2,而 y 则不是一直是 3,y 的值是上次 fun 被调用时 y 被赋的值(类似静态变量 y 使用的实例参考本链接)。
用 g 和 y 在 SRAM 中的位置也可以解释不同:g 处于 SRAM 的 stack 区域,只有 fun 函数被调用时才给 g 分配空间,并且每次分配后,都会对 g 赋初始值 2,所以每次调用 fun 函数,g 初始值都是2,并且函数 fun 执行完后下一句指令(pull)会自动注销 g 在 stack 中的空间;而 y 处于 SRAM 的 .data 区域,y 在 SRAM中有一个固定的物理空间,即使 fun 函数执行完毕,y 在 SRAM 中还是有一个空间保留着 y 的值,static int y = 3; 这句其实和定义全局变量初始值一样,只是在编译时将 y 放在了 .data段,并在 flash 中保存了其初始值 3, 这样在 MCU 启动时完成了对 y 的初始化,y 也就只在这时等于 3,之后 fun 执行中只要改变了 y 的值,下次再调用 fun 时,y 中还是上次的值,y=3; 这句代码完全不会执行。
static 变量详细介绍和使用可查看本链接。
对于常量
c 位置在 .text 段,程序运行期间 c 是在 flash 内,即程序要用到 c 时会去 flash 的 c 地址处直接取值。和全局变量 a 不同,启动代码不会 copy c 到 SRAM 中,SRAM 中不会有 c 的位置。
Code, RO, RW, ZI
这些只是用 keil 编译器时,对 .text, .data, .bss, .statck, .heap 这 5 段的不同称呼,详细介绍可参考本链接。简单总结如下:
Code:代码的大小
RO:常量所占空间
RW:程序中已经初始化的变量所占空间
ZI:未初始化的static和全局变量以及堆栈所占的空间
Flash占用大小=Code+RO+RW
SRAM占用大小=RW+ZI
参考链接:
https://blog.csdn.net/phenixyf/article/details/116718762
https://blog.csdn.net/lwbeyond/article/details/7275561#