“在编译时分配的内存”到底是什么意思?

新手上路,请多包涵

在 C 和 C++ 等编程语言中,人们经常提到静态和动态内存分配。我理解这个概念,但是“在编译期间分配(保留)所有内存”这句话总是让我感到困惑。

据我了解,编译将高级 C/C++ 代码转换为机器语言并输出可执行文件。如何在编译文件中“分配”内存?内存不是总是与所有虚拟内存管理的东西一起分配在 RAM 中吗?

根据定义,内存分配不是运行时概念吗?

如果我在我的 C/C++ 代码中创建一个 1KB 的静态分配变量,这是否会使可执行文件的大小增加相同的数量?

这是在“静态分配”标题下使用该短语的页面之一。

回归基础:内存分配,回顾历史

原文由 Talha Sayed 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

在编译时分配的内存意味着编译器在编译时解析,其中某些东西将在进程内存映射中分配。

例如,考虑一个全局数组:

 int array[100];

编译器在编译时知道数组的大小和 int 的大小,因此它在编译时知道数组的整个大小。此外,默认情况下,全局变量具有静态存储持续时间:它分配在进程内存空间的静态内存区域(.data/.bss 部分)中。鉴于该信息, 编译器在编译期间决定该数组将位于该静态内存区域的哪个地址

当然,内存地址是虚拟地址。该程序假定它拥有自己的整个内存空间(例如从 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 字节的零初始化,因为标准规定静态存储的东西应该默认初始化为零:

8.5.9(初始化器)[注意]:

在任何其他初始化发生之前,在程序启动时,每个静态存储持续时间的对象都被初始化为零。在某些情况下,稍后会进行额外的初始化。

我总是建议人们反汇编他们的代码,看看编译器对 C++ 代码的真正作用。这适用于存储类/持续时间(如这个问题)到高级编译器优化。您可以指示您的编译器生成程序集,但是 Internet 上有很多很棒的工具可以以友好的方式执行此操作。我最喜欢的是 GCC Explorer

原文由 Manu343726 发布,翻译遵循 CC BY-SA 3.0 许可协议

分享我对这个问题的了解。

你可以分两步理解这个问题:

  • 一、编译步骤: compiler 生成二进制。在Linux系统中,二进制是 ELF (Executable and Linkable Format) 格式的文件。 ELF 文件包含几个部分,包括 .bss.data
 .data
Initialized data, with read/write access rights

.bss
Uninitialized data, with read/write access rights (=WA)

.data.bss 只是映射到进程的内存布局段,其中包含静态变量。

  • 第二,加载步骤。当二进制文件被执行时, ELF 文件将被加载到进程的内存中。加载器可以从 ELF 文件中找到静态变量的信息。

简单来说,编译器和加载器遵循相同的标准相互通信,标准是ELF格式。

原文由 Chris Bao 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题