“Memory allocated at compile time” 究竟是什么意思?

193
在像C和C++这样的编程语言中,人们经常提到静态和动态内存分配。我理解这个概念,但是“所有内存都在编译时被分配(保留)”这个短语总是让我感到困惑。
就我所知,编译将高级别的C/C++代码转换为机器语言并输出一个可执行文件。在编译文件中如何“分配”内存?难道内存不总是通过虚拟内存管理等东西在RAM中分配吗?
按定义,内存分配不是一个运行时概念吗?
如果我在我的C/C++代码中创建了一个1KB的静态分配变量,那么可执行文件的大小是否会增加同样的数量?
这是其中一个页面上使用“静态分配”标题的页面。 回归基础:内存分配,一次历史之旅

在大多数现代架构中,代码和数据是完全分离的。虽然源文件中包含了代码和数据,但二进制文件只有对数据的引用。这意味着源文件中的静态数据只能作为引用来解析。 - Cholthi Paul Ttiopic
15个回答

2
如果你学习汇编语言编程,你会发现需要为数据、栈和代码等开辟段落。数据段是存放字符串和数字的地方,代码段是存放代码的地方。这些段落都被编译进可执行程序中。当然,栈大小也很重要……你不想出现堆栈溢出吧!
所以如果你的数据段是500字节,那么你的程序就有一个500字节的区域。如果你将数据段改为1500字节,程序的大小将增加1000字节。数据被组装成实际的程序。
这就是在编译高级语言时发生的事情。实际数据区在编译成可执行程序时被分配,从而增加了程序的大小。程序还可以动态地请求内存。你可以从RAM请求内存,CPU会给你使用,你可以释放它,垃圾回收器会将其释放回CPU。如果需要,好的内存管理器甚至可以将其交换到硬盘上。这些功能是高级语言提供给你的。

1

1
我希望通过几个图示来解释这些概念。
确实,内存无法在编译时分配。 但是,在编译时实际上会发生什么呢?
下面就是解释。 例如,一个程序有四个变量x、y、z和k。 现在,在编译时,它只是制作了一个内存映射,确定了这些变量相对于彼此的位置。 这张图将更好地说明它。
现在想象一下,没有任何程序运行在内存中。 我用一个大空矩形表示这一点。

empty field

接下来,将执行该程序的第一个实例。您可以将其视为以下内容。这是实际分配内存的时间。

first instance

当该程序的第二个实例正在运行时,内存情况如下。

second instance

第三个是...

third instance

我希望您能明白这个概念,以下是可视化展示。所以一直循环下去。

3
如果这些图表展示静态内存和动态内存之间的区别,我认为它们会更有用。 - Bartek Banachewicz
我有意避免这个,以使事情简单化。我的重点是清晰地解释这个基本概念,避免太多的技术细节。至于静态变量,则已经在之前的回答中得到了很好的阐明。因此,我跳过了这一部分。 - user3258051
2
嗯,这个概念并不特别复杂,所以我不明白为什么要把它简化到不必要的程度,但既然它只是作为一个补充答案,那就可以了。 - Bartek Banachewicz

0

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

您可以通过以下两个步骤来理解此问题:

  • 首先是编译步骤:编译器生成二进制文件。在Linux系统中,二进制文件是以ELF(可执行和可链接格式)格式的文件。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 格式。


0

编译器的其中一个功能是创建和维护一个符号表(在section.symtab下)。这将纯粹由编译器使用任何数据结构(列表、树等)创建和维护,而不是为开发人员所见。任何开发人员的访问请求都会首先命中此处。

现在关于符号表,我们只需要知道两列:符号名称和偏移量。

符号名称列将包含变量名称,偏移量列将包含偏移值。

让我们通过一个例子来看看:

int  a  ,  b  ,  c  ;

现在我们都知道寄存器Stack_Pointer(sp)指向堆栈内存的顶部。假设它是sp = 1000。

现在符号名称列中将有三个值,分别为a、b和c。提醒大家,变量a将位于堆栈内存的顶部。

因此,a的等效偏移值将为0。(编译时偏移值)

然后b及其等效偏移值将为1。(编译时偏移值)

然后c及其等效偏移值将为2。(编译时偏移值)

现在计算a的物理地址(或)运行时内存地址= (sp + a的偏移值) = (1000 + 0) = 1000

现在计算b的物理地址(或)运行时内存地址= (sp - b的偏移值) = (1000 - 1) = 996

现在计算c的物理地址(或)运行时内存地址= (sp - c的偏移值) = (1000 - 2) = 992

因此,在编译时,我们只有偏移值,只有在运行时才会计算实际的物理地址。

注意: 只有在程序加载后,Stack_Pointer的值才会被分配。指针算术运算发生在Stack_Pointer寄存器和变量偏移之间,以计算变量的物理地址。
        "POINTERS AND POINTER ARITHMETIC, WAY OF THE PROGRAMMING WORLD"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接