说明:
- Linux系统下编译产生的二进制程序是以ELF格式存储的,ELF格式是分段的;Linux系统采用段式内存管理架构,二进制程序加载进内存后内存分布也是分段的。
- windows系统也是类似的。
个人理解:
- 分段是由编译器和操作系统实现,编译时编译器将不同类型的元素存储到相应的段,以区分处理,利于管理和加快操作效率。
- 分段行为不是固定的,不同编译器,不同平台可能有细微差别,虚拟内存和硬盘都是连续的。
- 程序由数据和处理两部分组成,处理部分(代码)编译后为二进制指令(代码段)比较固定,而数据部分(变量)有多种形式,例如:全局变量,局部变量,static变量,const变量等等,为了实现这些需求和加快操作效率,因此数据段有多种分段类型。
一、代码:
- 段名:代码段(text段)
- 编译后生成给CPU执行的机器指令,一个程序只有一个代码段,只读,防止程序由于意外事故而修改自身指令。
- 该段也有可能包含一些只读的常量,例如字符串常量,const修饰的变量(各种单片机的编译器会这样存储),由编译器决定。
变量:
全局变量(生命周期等于整个程序执行期)
- 全局变量在编码时已经固定了,占用内存大小和值已确定,因为这些特性,全局变量可以在编译时就准备好,不需要等到运行时再去逐个分配内存设置值。
- 这里说的全局变量包括 全局变量,static的全局变量,static的局部变量。
- 全局变量由于初始值的不同,有以下两种情况。
二、已赋不为0的初始值:
- 段名:已初始化数据段(data段)。
- 初值不为0,编译器简单处理认为数据都不同,需要逐个变量保存到二进制程序文件中,程序运行时直接将整段数据拷贝进内存。
- 个人理解:编译器可以进一步优化,相同的值可以只存一份。
- 该段保存了程序中所有赋了初值并且初值不为0的全局变量,包括全局变量,static的全局变量,static的局部变量。
- 如果该段数据较多,会导致程序二进制文件非常大,如下:
三、未赋初值或者初值为0:
1、段名:未初始化数据段(bss段)。
2、由于可以将这些变量的初值处理成一样的,都设置为0,二进制程序文件就没必要存储这些值,只需要记录该段的首地址和段长度,程序运行加载进内存时再按这些参数申请和格式化内存就好。
3、该段保存程序中没有进行初始化或者初始化为0的全局变量;例如:
四、局部变量(生命周期由系统控制)
1、段名:栈。
2、由于局部变量不是固定的,无法在编译时进行处理。
3、 增长方向:自顶向下增长;普通的局部变量以及每次函数调用时所需要保存的信息(返回地址;环境信息)。
4、栈变量最大的特征是:栈变量都应该是临时变量,出栈后内存就非常可能被覆盖。
五、动态变量(生命周期由程序员控制)
1、段名:堆。
2、该段的大小并不固定,可动态扩张或缩减。
3、因为动态变量并不是固定的,无法在编译时进行处理。
4、动态存储区,是向高地址扩展的数据类型,是自下向上的扩展方式,动态内存分配的内存区域。
5、堆变量最大的特征是:大内存变量或者永久变量,临时小内存变量不应该放在堆内存中,应该放在栈内存中。
六、只读变量
1、段名:只读变量区域(rodata段)
2、只读的内存区域,const修饰的常量,以及常量字符串保存在该区域。例如:
存储时形态:
二进制程序中不包含堆,栈需要动态管理的段,程序加载进内存才会产生。
运行时形态:
1、每个进程都独自拥有4GB的虚拟内存空间,使用时再由系统转换成物理内存地址,该转换对用户是透明不可见的,因此时常讲的内存空间都是虚拟内存空间。
2、程序的代码段和一些全局变量是固定的,不会改变的,编译时会将代码段和这些变量的虚拟内存地址确定,因此每次运行都是固定的。
3、Linux下C程序的内存分布图如下,C程序将内存分为以下5部分,地址从低到高排列: