C程序中的内存分布
一个典型的C程序存储分区包含以下几类:
- Text段
- 已初始化数据段
- 未初始化数据段
- 栈
- 堆
1. Text段
Text段通常也称为代码段,由可执行指令构成,是程序在目标文件或内存中的一部分,Text段通常放在栈或堆的下面,以防止堆栈溢出篡改其数据。
通常情况下,Text段是可共享的,对于需要频繁调用的程序,其在内存中只需要一份拷贝即可,如文本编辑器、C编译器、Shell等,因此text段通常设为只读以防止程序的突发性的修改。
2. 已初始化数据段
已初始化数据段,通常简单称作数据段,数据段占据程序虚拟地址空间的一部分,内部包括全局变量、静态变量(程序负责初始化这些变量)。需注意的是,数据段不是只读的,在运行时变量值是可以变动的。
数据段还可以更细的分为初始化只读区以及初始化可读写区。
举例:全局字符串 char s[] = “hello world”,全局变量 int debug=1,静态变量 static int i = 10 存储在初始化可读写区;另一种情况下,const char* string = “hello world”,字符串“hello world”存储在初始化只读区,string指针则存在初始化可读写区。
3. 未初始化数据段
未初始化数据段,通常称作“bss”段,名字来源于古老的汇编操作符命名 “block started by symbol”,段内的数据在程序开始执行之前被内核初始化为0值,通常开始于已初始化数据段的末尾处。段内包含初始化为0的全局变量/静态变量以及源码中未显示进行初始化的变量。
举例:变量 static int i; 全局变量 int j; 包含在BBS段中。
4. 栈
栈与堆是相互毗邻的,并且生长方向相反;当栈指针触及到堆指针位置,意味着栈空间已经被耗尽(如今地址空间越来越大,及虚拟内存技术发展,栈与堆可能放置在内存的任何地方,但生长方向依然还是相向的)。
栈区域包含一个LIFO结构的程序栈,其通常放置在内存的高地址处,在x86架构中,栈朝地址0方向生长,在其它架构也可能朝着相反的方向生长。栈指针寄存器跟踪栈顶位置,每当有数值被压入栈中,栈顶指针会被调整,在一个函数的调用过程中,压入的一系列数值被称作“栈帧”,栈帧至少包含一个返回地址。
栈存储着自动变量以及每次函数调用时保存的信息,每当函数被调用时,返回地址以及调用者的上下文环境例如一些机器寄存器都存储在栈中,新的被调用函数此时会在栈上重新分配自动/临时变量,这就是递归函数的工作原理。每当函数递归调用自己时,都会使用新的栈帧,因此当前函数实体内的栈帧内的变量不会影响另外一个函数实体内的变量。
5. 堆
堆通常用作动态内存分配,堆空间起始于BSS段的末尾,并向高地址处生长,堆空间通常由malloc, realloc 及 free管理,这些接口可能再使用brk/sbrk系统调用来调整大小,在一个进程中,堆空间被进程内所有的共享库及动态加载模块所共享。
示例-查看可执行文件的存储分配
注:size命令以字节为单位统计可执行程序的text, data, 及bss段(更多详情参考man page of size(1))
1. 检查下述C程序
编译后查看text/data/bss分布情况
2. 增加一个全局变量
编译后观察变化,bss区域增加了4字节
3. 再添加一个静态变量
编译后再看变化,bss区域增加了4字节
4. 初始化3中的静态变量看看
此时变量存储到了data段,data增加了4字节,bss减少了4字节
5.继续初始化2中的全局变量看看
此时静态变量也存储到了data段,data增加了4字节,bss减少了4字节
6. 添加一个全局const变量看看
此时“haha”字符串存储到了text段,并增加了5个字符,多出来的一个字符是\0,ptr指针存储到了data段,增加了4个字节。
注:在步骤4/5中,初始化的值若为0,编译器还是会将变量放入BSS区。感兴趣的同学可以动手编译看看。
文章翻译/修改自 https://www.geeksforgeeks.org/memory-layout-of-c-program/