C中全局变量和static变量的存储与初始化

目标文件、可执行程序及其他二进制文件以ELF格式存储在磁盘中,该文件有两个重要的段(section),即代码段和数据段。
数据段又分为:.data 段 和 .bss段,其中.data段存储已初始化的全局变量和静态变量,.bss 段存储未初始化的全局变量。

在C中,凡是在任何代码块之外声明的变量总是存储于静态内存中,也就是不属于堆栈的内存,这类变量称为静态(static)变量。——C和指针(p43)
这句话中有两个概念需要搞清楚:
  • 静态内存:指的是前文所述的数据段(.data 段 和 .bss段),不包括堆栈。
  • 静态(static)变量:没有static关键字的变量也可以是静态变量,关键是存储于静态内存中。实际上,当static关键字用于不同上下文环境时,具有不同的意思。
    同时,我们还知道,静态变量在程序运行之前被创建,其分为两种:已初始化的 和 未初始化的。
    • 已初始化的包括:static变量 (这里不能称其为静态变量)、 已初始化的全局变量;static变量是默认zero-initialization的,所以就算未显示初始化,也会被zero-initialization; 全局变量 <=字面值(或常量),即赋予字面值时会在编译阶段就被初始化,如:int g_var = 4;
    • 未初始化的:即未初始化的全局变量。
      如前文所述,前者存储于.data段,后者存储于.bss段。且前者的初始化是在编译阶段就完成的,后者的初始化时刻不一定,我们在后文中讨论。但两者的共同点是,都是在运行前创建的。
这时,我们也许想问,运行前是如何创建静态变量的?
我们知道,编译-汇编后生成二进制目标文件(ELF格式),虽然还不是最终的可执行文件,但已经有了代码段和数据段,而此时数据段中的.data段中已经有了‘已初始化的静态变量’的磁盘存储空间,并被填充了初始化值,在进程加载时直接被映射至内存空间中。
【C中全局变量和static变量的存储与初始化】而.bss段实际上未占据任何磁盘存储空间,也就是徒有其名,无有其实,只是在ELF的section header table 中记录其应该分配到的磁盘存储空间,而直到可执行程序被加载到内存中时,加载器将依据.bss段的section header中的信息,在内存中为其分配空间。
总结来说就是,运行前创建分为两种:编译时创建——>加载时映射至内存;加载时创建。
此时,我们仍有疑惑,静态变量的创建肯定是在运行前了,那么未初始化变量的初始化到底是在什么时候进行的?
有三个猜测:
  • 链接时?前文中讲,直至进程加载至内存时才分配空间,自然无谈初始化。
  • 加载进程至内存时:
(C和指针)在静态变量的初始化中,我们可以把可执行文件想要初始化的值放在当程序执行时变量将会使用的位置(注:实际上就是磁盘文件.data 段),当可执行文件载入到内存时,这个已经保存了正确初始值的位置将赋值给那个变量……如果不显式地指定其初始值,静态变量将初始化为0。
这段话说的比较晦涩,因为它不想引入过多程序装载过程的知识。但我们也可以借此判断,未初始化的全局变量实际上和static一样会被初始化为0,只不过它是在可执行文件载入到内存时发生的。
  • 运行时,所谓运行时初始化,即动态初始化(dynamic-initialization),然而C与C++不同,静态变量不支持动态初始化。所以运行时也是不可能的。
//in C int x = 5; static y = x; //error//in C++ int x = 5; static y = x; //correct

最终总结:
在C中,静态变量,即全局变量和static变量,是在程序运行前创建的,其中已初始化的全局变量和static变量在编译汇编成目标文件时,初始值就已经保存在磁盘的.data段了,进程加载时将其映射到内存空间即可;
未初始化的全局变量需要进程加载时真正的为.bss段分配内存空间,并赋值为0。静态变量的创建和初始化都是在运行前完成的,切记C中不能动态初始化,这一点与C++不同。

    推荐阅读