编译器如何为变量分配内存地址?

我教一门课程让学生们提出有关编程的问题(!):我有这个问题:

机器为什么选择变量进入内存? 我们可以告诉它存储变量的位置吗?

我真的不知道该说些什么。 这是我的第一次尝试:

编译器(不是机器)自动选择将变量存储在进程地址空间的位置。 使用C,我们无法告诉机器存储变量的位置。

但是“自动”有点虎头蛇尾并且引发了一个问题…而且我已经意识到我甚至不知道它是编译器,运行时或操作系统还是谁来完成任务。 也许有人能比我更好地回答学生的问题。

这个问题的答案非常复杂,因为根据可变范围,大小和编程环境,存在各种内存分配方法。

堆栈分配的变量

通常, local variables放在“堆栈”上。 这意味着编译器为“堆栈指针”分配一个偏移量,它可以根据当前函数的调用而有所不同。 即编译器假定程序可以访问和使用诸如Stack-Pointer + 4,Stack-Pointer + 8等存储器位置。 从函数return ,不保证存储器位置保留这些值。

这将映射到类似于以下的汇编指令。 esp是堆栈指针, esp + N指的是相对于esp的内存位置:

 mov eax, DWORD PTR SS:[esp] mov eax, DWORD PTR SS:[esp + 4] mov eax, DWORD PTR SS:[esp + 8] 

然后是堆分配的变量。 这意味着有一个库调用来从标准库请求内存(在C ++中为alloc或在C ++中为new )。 该存储器保留到程序执行结束。 allocnew返回指向内存区域中的内存,称为堆。 分配函数必须确保不保留内存,这有时会使堆分配变慢。 此外,如果您不想耗尽内存,则应free (或delete )不再使用的内存。 内部堆分配非常复杂,因为标准库必须跟踪内存中已使用和未使用的范围以及释放的内存范围。 因此,即使释放堆分配的变量也比分配它更耗时。 有关更多信息,请参阅如何在内部实现malloc()?

理解堆栈和堆栈之间的区别对于学习如何使用C和C ++编程非常重要。

任意指针

天真地可以假设,通过设置指向任意地址int *a = 0x123 ,应该可以解决计算机内存中的任意位置。 这并不完全适用,因为(取决于CPU和系统)程序在寻址内存时受到严格限制。

感受记忆

在引导式课堂体验中,通过将源代码编译为汇编程序来探索一些简单的C代码可能是有益的(gcc可以这样做)。 一个简单的函数,如int foo(int a, int b) { return a+b;}就足够了(没有优化)。 然后看看像int bar(int *a, int *b) { return (*a) + (*b);} ;

调用bar时,在堆栈上分配一次参数,每个malloc一次。

结论

编译器确实执行一些相对于基本地址的变量位置和对齐,这些地址由程序/标准库在运行时获得。

要深入理解与记忆相关的问题,请参阅Ulrich Drepper的“每个程序员应该了解的内容” http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.91.957

除了C-ish Country idenote

然后还有垃圾收集,它在许多脚本语言(Python,Perl,Javascript,lisp)和设备独立环境(Java,C#)中很流行。 它与堆分配有关,但稍微复杂一些。

各种编程语言只是基于堆(无堆栈python)或完全基于堆栈(第四)。

我认为这个问题的答案首先要了解内存中程序的布局。 在操作系统下面,计算机的主内存只是一个巨大的arrays。 当您运行程序时,操作系统将占用该内存的一大块并将其分解为逻辑部分,以用于以下目的:

  • stack:这个内存区域存储有关当前所有函数的信息,包括当前运行的函数及其所有祖先。 存储的信息包括局部变量和函数完成时返回的地址。

  • heap:当你想动态分配一些存储时,使用这个内存区域。 通常,您的局部变量将在堆中存储数据的地址(即,它将是一个指针),并且您可以将此地址发布到程序的其他部分,而不必担心当前的数据会被覆盖function超出范围。

  • 数据,bss,文本段:这些或多或少都在这个特定问题的范围之外,但它们存储诸如全局数据和程序本身之类的东西。

希望有所帮助。 在线也有很多好的资源。 我只是用Google搜索“内存中程序的布局”并找到了这个: http : //duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory

函数中的局部变量通常将在函数的堆栈帧中按顺序排列。

我想补充几点。 对于您知道内存映射和运行地址的固件,以及使用您自己的链接器脚本编译源代码的位置 –

您可以使用section属性为变量分配自定义部分,然后通过链接描述文件将特定地址分配给自定义部分。 然后变量将获得固定/分配的地址。