本文的demo是在linux环境下编译解析的,cpu是x86-64
首先我们先写一个功能简单的demo-SimpleSection.c。这个demo中有一个func1
函数用来打印数据,一个已经初始化的全局变量global_init_var
和未初始化的全局变量global_uninit_var
,一个已初始化的局部静态变量static_var
和一个未初始化的局部静态变量static_var2
。
int printf(const char* format, ...);
int global_init_var = 84;
int global_uninit_var;
void func1(int i)
{
printf("%d\n", i);
}
int main(void)
{
static int static_var = 85;
static int static_var2;
int a = 1;
int b;
func1(static_var + static_var2 + a + b);
return a;
}
下一步我们把代码文件编译成目标文件: gcc -c SimpleSection.c
接着使用使用objdump
工具查看目标文件:objdump -h SImpleSection.o
从上述截图中可以看出,objdump得到的 目标文件信息共有8项,前六项0~5分别是:
- 代码段
- 数据段
- BSS段
- 只读数据段
- 注释信息段
- 堆栈提示段
信息项共有5列,分别为Size、VMA、LMA、File off 和 Algn。其中Size为段的长度,File off为段的偏移也就是段的位置。
下面我们用一张图来标识这几个段的相对位置。
从图中可以清晰的看到各个段在目标文件中的分布。值得注意的是.bss
段并不存在,我们在上一张截图中就可以看到Size一列中.bss
显示的是ALLOC而不是CONTENTS。这表示在ELF文件中不存在内容。因此.bss
段中只记录需要分配的空间大小,而不是真正存在于ELF文件的空间大小。还有就是.text
段的起始地址是0x 0000 0040
,段size为0x5f
,但是奇怪的是0x40+0x5f=0x9f
而不是0xa0
,这是因为对齐的缘故。
为了更加清晰的查看.text
段的信息,我们使用objdump -s -d SimpleSection.o
命令去反汇编ELF文件,从而与上面的表述向印证。
如上述截图所示,.text
段的最后一个字节的偏移量,也就是位置是0x5f
。这与前面我们看到的.text
段的大小一致。
对于.data
段,一共有8个字节,对应了代码中的int global_init_var
和static int static_var
变量。前4个字节为0x 54 00 00 00 00
,其中第一个字节为0x54
,相应的十进制为84,对应了代码中int global_init_var
变量。但是需要注意的是这里使用了小端字节序,所以0x54
字节才会在第一个。类比一下就知道后4个字节表示的是static int static_var
变量。
通常编译器会把字符串常量和其他的一些常量(在c++中用const修饰)放在.rodata
段中,这样有一下几个好处:
- 在语义上支持c++的const关键字
- 可以在操作系统加载程序的时候将
.rodata
段的属性映射成只读,保证程序的安全性 - 在某些嵌入式平台下,有些存储区采用只读存储器,如ROM,保证了程序访问存储器的正确性
但是需要注意的是,某些编译器会把字符串常量放到.data
段,如MSVC编译器。