C全局静态变量初始化是由链接器完成的吗?

假设我们有:

在f1.c

#include  static int x = 10; void f1() { printf("f1.c : %d\n", x); } 

main.c中

 extern void f1(); int main(int argc, char **argv) { f1(); return 0; } 

我们将编译和读取两个ELF文件符号表(rel.ELF和exec ELF):

 $> gcc -c *.c $> readelf -s f1.o | grep x Num: Value Size Type Bind Vis Ndx Name 5: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 x $> gcc *.o $> readelf -s a.out | grep x Num: Value Size Type Bind Vis Ndx Name 38: 0000000000601038 4 OBJECT LOCAL DEFAULT 25 x 

我可以通过读取可重定位目标文件f1.o看到全局静态变量x0000000000000000Value (也称为地址)。
这意味着我们还没有初始化它,因为它仍然是一个rel。 ELF目标文件和链接器将处理此问题。

那么我的问题是,如果链接器是在链接0000000000601038之后将x设置为已知地址的值10,那么它是如何做到的呢? 链接器在哪里获取信息以将值设置为10以及谁提供此信息( f1.o ?)?

0000000000000000 (在目标文件f1.o )是(静态变量的) 相对地址,因此是偏移量,该文件还包含与之相关的重定位指令。 获取参数x以打印的代码也有一些重定位(在某些加载机器指令上)。

在该目标文件中,您可能有一个.data部分。 该部分应以包含10的单词(在f1.o观察到0偏移) f1.o

阅读更多关于连接器的信息 (我推荐Levine的连接器和装载器书)。 链接过程(获取ELF可执行文件)是处理重定位指令。 阅读更多关于ELF格式的信息,从elf(5)开始 (阅读ELF wikipage之后)。 还要研究ABI规范(对于Linux x86-64,请参见此答案),其中详细说明了可能的重定位指令。

您可能希望使用gcc -Wall -S -fverbose-asm -O1 f1.c编译gcc -Wall -S -fverbose-asm -O1 f1.c然后查看发出的汇编程序文件f1.s

您可能还想使用各种工具(如readelf(1)和objdump(1))检查目标文件f1.o和ELF可执行文件a.out 。 两者都接受了许多选项(特别是objdump-r选项以显示重定位指令)。

动态链接 ( C标准库 libc.*.so )在ELF可执行文件中引入了一些额外的复杂性。 另请参阅ld-linux(8) (在运行时启动时执行某些链接作业)和vdso(7) 。 您可能还想阅读Drepper的“ 如何编写共享库”文章。

免费提供的教科书操作系统:三个简单的片段也值得阅读(它解释了一个过程是什么以及它的执行方式)。

存储具有特定值的静态存储持续时间变量的此段称为.data (这是ELF标准使用的名称,但其他链接器也倾向于使用相同的名称)。

如何设置这些变量取决于目标系统。

  • 在基于RAM的系统(例如PC)上,整个.data段作为可执行文件的一部分提前初始化,并与程序一起加载到RAM中。
  • 在基于ROM的系统(例如带闪存的微控制器)上, .data无法提前初始化。 而是在调用main()之前,通过一些启动代码(“CRT”)将其从ROM复制到RAM。 所以它实际上是在运行时设置的,这意味着在这样的系统上程序启动总会有延迟。 为了摆脱延迟,通常会有一个非标准的启动替代方案(“最小”),它完全跳过静态存储持续时间变量的初始化。