在 C 和 C++ 等编程语言中,人们经常提到静态和动态内存分配。我理解这个概念,但是“在编译期间分配(保留)所有内存”这句话总是让我感到困惑。
据我了解,编译将高级 C/C++ 代码转换为机器语言并输出可执行文件。如何在编译文件中“分配”内存?内存不是总是与所有虚拟内存管理的东西一起分配在 RAM 中吗?
根据定义,内存分配不是运行时概念吗?
如果我在我的 C/C++ 代码中创建一个 1KB 的静态分配变量,这是否会使可执行文件的大小增加相同的数量?
这是在“静态分配”标题下使用该短语的页面之一。
回归基础:内存分配,回顾历史
在编译时分配的内存意味着编译器在编译时解析,其中某些东西将在进程内存映射中分配。
例如,考虑一个全局数组:
int array[100];
编译器在编译时知道数组的大小和 a 的大小int,因此它在编译时知道数组的整个大小。此外,全局变量默认具有静态存储持续时间:它分配在进程内存空间的静态内存区域(.data/.bss 部分)中。鉴于该信息, 编译器在编译期间决定该数组将位于该静态内存区域的哪个地址 。
int
当然,内存地址是虚拟地址。 该程序假定它拥有自己的整个内存空间(例如从 0x00000000 到 0xFFFFFFFF)。这就是为什么编译器可以做出诸如“好的,数组将位于地址 0x00A33211”之类的假设。在运行时,MMU 和 OS 将地址转换为实际/硬件地址。
值初始化静态存储的东西有点不同。例如:
int array[] = { 1 , 2 , 3 , 4 };
在我们的第一个示例中,编译器仅决定数组的分配位置,并将该信息存储在可执行文件中。 在值初始化的情况下,编译器还将数组的初始值注入可执行文件,并添加代码告诉程序加载器在程序启动时分配数组后,应该用这些值填充数组。
以下是编译器生成的程序集的两个示例(GCC4.8.1 with x86 target):
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 字节的零初始化,因为标准规定静态存储的东西应该默认初始化为零:
a
8.5.9 (Initializers) [注意]: 每个静态存储持续时间的对象在程序启动时在任何其他初始化发生之前都被初始化为零。在某些情况下,稍后会进行额外的初始化。
我总是建议人们反汇编他们的代码,看看编译器对 C++ 代码的真正作用。这适用于存储类/持续时间(如这个问题)到高级编译器优化。您可以指示您的编译器生成程序集,但是 Internet 上有很多很棒的工具可以以友好的方式执行此操作。我最喜欢的是GCC Explorer。