可使用命令readelf-segments executableFile查看文件的段信息。ELF文件的主要段有:
●代码段(text):用于存放函数命令。
●数据段(data):用于存放已经初始化的全局变量和静态变量。
●只读数据段(rodata):用于存放只读常量或const关键字标识的全局变量。
●bss段(block started by the symbol):用于存放未初始化的全局变量和静态变量,这些变量由于未初始化,所以没有必要在ELF文件中为其分配空间,bss段的长度总为0。
●调试信息段(debug):用于存放调试信息。
●行号表(line):用于存放编译器代码行号和命令的对应关系。
●字符串表(strtab):用于存储ELF文件中的各种字符串。
●符号表(symtab):用于ELF文件中各种的符号。
ELF文件的运行是指将硬盘上存储的文件调入并装载进内存,程序中各种段就会被装载在内存地址空间中,形成自己的内存空间布局。其中,ELF文件的PT_LOAD段是可加载的段,分别由p_filesz和p_memsz指定文件的大小和内存的大小。
在内存模型中,进程的内存空间主要由6部分组成,如图2.13所示。
图2.13 进程内存空间的组成
其中,存储在.rodata中的数据是只读的,存储的是常数(带const修饰符)和字符串。局部变量和函数参数分别在栈中分配(栈和堆分别在内存中分配,在ELF文件中不存在对应的部分)。
查看各个段的装载内存布局命令如下:
全局变量和静态变量存储在.data和.bss中。其中,未被初始化的存储在.bss中,已被初始化(不为0)的存储在.data中。这些变量存储的区域是静态存储区。
我们将Listing 2.14作为基准源码,看看全局变量和静态变量的存储。
Listing 2.14 测试.text、.bss和.data的基准源码cMemoryLayout-simple.c
全局数量和静态变量的存储如下:
Listing 2.15 在基准源码上增加未被初始化的全局变量的源码cMemoryLayout-simplev2.c
未被初始化的全局变量的存储如下:
Listing 2.16 在基准源码上增加未被初始化的全局变量和静态变量的源码cMemoryLayout-simplev3.c
未被初始化的全局变量和静态变量的存储如下:
Listing 2.17 在基准源码上增加未被初始化的全局变量和初始化静态变量的源码cMemoryLayout-simplev4.c
未被初始化的全局变量和初始化静态变量的存储如下:
Listing 2.18 在基准源码上增加已被初始化的全局变量和静态变量的源码cMemoryLayout-simplev5.c
已被初始化的全局变量的存储如下:
Listing 2.19 在基准源码上增加const全局变量的源码cMemoryLayout-simplev6.c
const全局变量的存储如下:
Listing 2.20 在基准源码上增加const全局变量和字符串常量的源码cMemoryLayout-simplev7.c
const全局变量和字符串常量的存储如下:
局部变量和函数参数是在函数调用过程中动态申请和释放的,存储在栈中。栈从高地址往低地址增长。程序的运行可以没有堆(Heap),但必须得有栈(Stack)。当调用函数时就会在栈上新建一个栈帧,当函数调用结束时,栈帧就会从栈上移除。
用户通过malloc/realloc/calloc申请的空间在堆中,通过free()函数释放堆空间。栈和堆都是动态存储区。
Listing 2.21 进程在堆和栈空间布局的测试源码stackHeapTest.c
Listing 2.21对应的例程在栈和堆中的布局如图2.14所示。
图2.14 Listing 2.21对应的进程在堆和栈中的布局