我教一门课程让学生提出有关编程的问题(!):我有这个问题:
为什么机器选择变量进入内存?我们能告诉你吗? 它存储变量的位置?
我真的不知道该说些什么。这是我的第一次尝试:
编译器(不是机器)选择将变量存储在进程地址中的位置 空间自动。使用C,我们无法告诉机器存储变量的位置。
但是“自动”有点虎头蛇尾并且引发了一个问题...而且我已经意识到我甚至不知道它是编译器还是运行时或操作系统,还是谁做了任务。也许有人能比我更好地回答学生的问题。
答案 0 :(得分:28)
这个问题的答案非常复杂,因为根据变量的范围,大小和编程环境,存在各种内存分配方法。
通常local variables
放在“堆栈”上。这意味着编译器为“堆栈指针”分配一个偏移量,根据当前函数的调用,它可以是不同的。即编译器假定程序可以访问和使用堆栈指针+ 4,堆栈指针+ 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
)请求内存。该存储器保留到程序执行结束。 alloc
和new
返回指向称为堆的内存区域中的内存指针。分配函数必须确保不保留内存,这有时会使堆分配变慢。此外,如果您不想耗尽内存,则应free
(或delete
)内存不再使用。内部堆分配非常复杂,因为标准库必须跟踪内存中已使用和未使用的范围以及释放的内存范围。因此,即使释放堆分配的变量也比分配它更耗时。有关详细信息,请参阅How is malloc() implemented internally?
了解堆栈和堆栈之间的区别对于学习如何使用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一次。
编译器确实执行了一些相对于base-adresses的变量placment和alignment,这些是在运行时由程序/标准库获得的。
要深入了解与记忆相关的问题,请参阅Ulrich Drepper的“每位程序员应该了解的关于记忆的内容”http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.91.957
然后还有垃圾收集,它在许多脚本语言(Python,Perl,Javascript,lisp)和设备无关环境(Java,C#)中很流行。它与堆分配有关,但稍微复杂一些。
各种编程语言只是基于堆(无堆栈python)或完全基于堆栈(第四版)。
答案 1 :(得分:7)
我认为这个问题的答案始于对内存中程序布局的理解。在操作系统下面,计算机的主内存只是一个巨大的阵列。当您运行程序时,操作系统将占用该内存的一大块并将其分解为逻辑部分,用于以下目的:
stack:这个内存区域存储有关当前所有函数的信息,包括当前运行的函数及其所有祖先。存储的信息包括局部变量和函数完成时返回的地址。
heap:当你想动态分配一些存储空间时,会使用这个内存区域。通常,您的局部变量将在堆中存储数据的地址(即,它将是一个指针),并且您可以将此地址发布到程序的其他部分,而不必担心当前的数据会被覆盖功能超出范围。
数据,bss,文本片段:这些或多或少超出了这个特定问题的范围,但它们存储了诸如全局数据和程序本身之类的东西。
希望有所帮助。网上也有很多好的资源。我只是用谷歌搜索“内存中程序的布局”,发现了这个:http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory
答案 2 :(得分:0)
函数中的局部变量通常会在函数的堆栈框架中按顺序排列。
答案 3 :(得分:0)
我想补充几点。对于您知道内存映射和运行地址的固件,以及使用您自己的链接描述文件编译源代码的位置 -
您可以使用section属性为变量分配自定义部分,然后通过链接描述文件将特定地址分配给自定义部分。然后变量将获得固定/分配的地址。