“在编译时分配的内存”真的意味着什么?
在像C和C ++这样的编程语言中,人们通常会引用静态和动态内存分配。 我理解这个概念,但是“在编译期间分配(保留)所有内存”这句话总是令我困惑。
据我了解,编译将高级C / C ++代码转换为机器语言并输出可执行文件。 在编译的文件中如何“分配”内存? 是不是所有的虚拟内存管理内存总是分配在内存中?
按定义,内存分配是不是运行时概念?
如果我在C / C ++代码中创建一个1KB静态分配的变量,是否会增加相同数量的可执行文件的大小?
这是在“静态分配”标题下使用短语的页面之一。
回到基础:内存分配,走下历史
在编译时分配的内存意味着编译器在编译时解析某些内容将在进程内存映射中分配的地方。
例如,考虑一个全局数组:
int array[100];
编译器在编译时知道数组的大小和int
的大小,因此它在编译时知道数组的整个大小。 此外,全局变量默认情况下具有静态存储持续时间:它分配在进程内存空间的静态内存区域(.data / .bss节)中。 根据这些信息, 编译器会在编译期间决定该阵列将在哪个静态存储区中的哪个地址 。
当然,这些内存地址是虚拟地址。 该程序假定它有自己的整个内存空间(例如,从0x00000000到0xFFFFFFFF)。 这就是为什么编译器可以做一些假设,比如“好吧,数组将在地址0x00A33211”。 在运行时,MMU和OS将地址转换为真实/硬件地址。
值初始化静态存储的东西有点不同。 例如:
int array[] = { 1 , 2 , 3 , 4 };
在我们的第一个例子中,编译器只决定了数组的分配位置,将该信息存储在可执行文件中。
在值初始化的情况下,编译器还会将数组的初始值注入到可执行文件中,并添加代码,告诉程序加载程序在程序启动时数组分配后应该填充这些值。
以下是由编译器生成的程序集的两个示例(带x86目标的GCC4.8.1):
C ++代码:
int a[4];
int b[] = { 1 , 2 , 3 , 4 };
int main()
{}
输出组件:
a:
.zero 16
b:
.long 1
.long 2
.long 3
.long 4
main:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
popq %rbp
ret
如你所见,这些值直接注入到程序集中。 在数组a
,编译器生成16个字节的零初始值,因为标准说默认情况下静态存储的东西应该初始化为零:
8.5.9(初始化程序)[注意]:
在进行任何其他初始化之前,程序启动时,静态存储持续时间的每个对象均为零初始化。 在某些情况下,稍后会进行额外的初始化。
我总是建议人们反编译他们的代码,看看编译器真正用C ++代码做什么。 这从存储类/持续时间(如此问题)适用于高级编译器优化。 您可以指示您的编译器生成程序集,但有一些很好的工具可以以友好的方式在Internet上执行此操作。 我最喜欢的是GCC Explorer。
在编译时分配的内存仅仅意味着在运行时不会有进一步的分配 - 不会调用malloc,new或其他动态分配方法。 即使你不需要所有的内存,你也会有固定的内存使用量。
按定义,内存分配是不是运行时概念?
内存在运行时间之前未被使用,但在执行之前立即开始其分配由系统处理。
如果我在C / C ++代码中创建一个1KB静态分配的变量,是否会增加相同数量的可执行文件的大小?
简单地声明静态不会增加超过几个字节的可执行文件的大小。 用非零的初始值声明它(为了保持初始值)。 链接器只是简单地将这个1KB数量添加到系统加载程序在执行之前立即为您创建的内存要求。
在编译时分配的内存意味着当您加载程序时,内存的一部分将立即分配,并且在编译时确定此分配的大小和(相对)位置。
char a[32];
char b;
char c;
这3个变量是“在编译时分配的”,这意味着编译器在编译时计算它们的大小(这是固定的)。 变量a
将是内存中的偏移量,比方说,指向地址0, b
将指向地址33, c
指向34(假设没有对齐优化)。 因此,分配1Kb的静态数据不会增加代码的大小,因为它只会改变它内部的偏移量。 实际空间将在加载时分配。
真正的内存分配总是在运行时发生,因为内核需要跟踪它并更新其内部数据结构(为每个进程分配多少内存,页面等等)。 不同之处在于编译器已经知道您要使用的每个数据的大小,并且只要程序执行时就会分配这些数据。
请记住我们正在谈论相对地址。 变量的实际地址将会不同。 在加载时,内核将为进程保留一些内存,比如说在地址x
,并且可执行文件中包含的所有硬编码地址都将增加x
字节,以便示例中的变量a
位于地址x
, b在地址x+33
等等。
上一篇: What does "Memory allocated at compile time" really mean?