什么时候在 C/C++ 中分配静态内存?在编译时还是在程序运行的最开始?

cat*_*ood 3 c c++ memory static allocation

不同的来源对我说不同的事情 - 一些 StackOverflow 答案说它是在编译时分配的 - 其他人说它是在编译时“定义”的,并在运行时开始分配(“加载时间”是一些人所说的) ,而其他人则说它是在编译时分配的。什么时候在 C/C++ 中准确分配静态内存?(如果它与“定义”变量有关 - 有人可以告诉我在内存级别“定义”变量意味着什么 - 将不胜感激!)

另外,您将如何在运行时设置指向已分配静态内存开头的指针?

Eri*_*hil 9

在典型的工具中,具有静态存储持续时间的内存分为多个步骤:

  • 编译器在目标模块中生成数据(可能通过某种形式的汇编代码),描述对各种内存的需求:内存初始化为零,内存初始化为特定值并在此之后为只读,内存初始化为特定值并且可能被修改,不需要初始化的内存,以及其他可能的。编译器还根据需要包含初始数据、有关引用所需内存中不同位置的符号的信息以及其他信息。此时,内存分配的形式大致类似于“常量数据部分需要8个字节,并且foo应该将调用的符号设置为它们的地址”。
  • 链接器将这些信息组合成一个可执行文件中的类似信息。它还解析有关符号的部分或全部信息。此时,内存的分配形式为“初始化的非常量数据段需要3048字节,这里是它的初始数据。当它被分配一个虚拟地址时,应该调整以下符号:bar在距段开头的偏移量 124 处,baz在偏移量 900 处……”
  • 程序加载器读取这些信息,为其分配虚拟地址空间中的位置,并可能将可执行文件中的一些数据读入内存或通知操作系统在需要时在哪里可以找到数据。此时,代码中引用各种符号的位置已根据这些符号的最终值进行了修改。
  • 操作系统为虚拟地址分配物理内存。通常,当进程尝试访问特定页面中的内存时,这是“按需”分块(内存页面)完成的,而不是在程序最初加载时完成。

总而言之,静态内存不会在任何特定时间分配。它是许多活动的结合。对程序的影响主要在于它的发生与程序启动时所有分配的情况相同,但物理内存可能仅在指令实际执行之前分配。(物理内存甚至可以从进程中取出并在以后恢复。)


Ant*_*ala 8

C 标准仅说明了这一点:

C11 5.1.2p1

[...]所有具有静态存储持续时间的对象都应在程序启动前初始化(设置为其初始值)。否则未指定此类初始化的方式和时间。

C11 6.2.4p2-3

2 对象的生命周期是程序执行期间保证为其保留存储空间的部分。一个对象存在,有一个常量地址,33) 并在其整个生命周期中保留其最后存储的值。34) 如果一个对象在其生命周期之外被引用,则行为是未定义的。当指针指向(或刚刚过去)的对象到达其生命周期结束时,指针的值变得不确定。

3 一个对象,其标识符在没有存储类说明符 _Thread_local 的情况下声明,并且具有外部或内部链接或存储类说明符 static,具有静态存储持续时间。它的生命周期是程序的整个执行过程,它的存储值在程序启动之前只初始化一次。

但是……这被as-if规则进一步复杂化,实际的实现只需要在可观察到的副作用范围内执行此操作。

事实上,例如在 Linux 中,人们可能会争辩说,具有静态存储期的变量是在生成可执行文件时由编译器和链接器初始化和分配的。当程序运行时,动态链接器 ( ld.so) 然后准备程序段,以便初始化数据mmap从可执行映像内存映射 ( ) 到 RAM,默认(零初始化)数据从零页映射。

虽然虚拟内存是由编译器、链接器和动态链接器分配的,但实际的可写 RAM 页帧仅在您第一次写入页面上的变量时才分配......

但在基本情况下您不需要了解这一点。就好像具有静态存储持续时间的变量的内存是在main进入之前分配和初始化的,即使实际情况并非如此。


Jos*_*hua 6

静态内存分两步分配。

步骤 1 由链接器执行,因为它布置可执行映像并说明静态变量在相对地址空间中的位置。

步骤 2 由加载程序在实际分配进程内存时执行。

在 C++ 中,静态对象在进入main. 如果你不小心你的代码,你可以看到仍然为零的对象,即使它们有总是会改变它的构造函数。(编译器会尽可能多地进行常量评估,因此玩具示例不会显示它。)