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

Der*_*unk 25 c compiler-construction linker operating-system

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

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

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

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

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

wir*_*bel 29

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

堆栈分配的变量

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

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

mov eax, DWORD PTR SS:[esp]
mov eax, DWORD PTR SS:[esp + 4]
mov eax, DWORD PTR SS:[esp + 8]
Run Code Online (Sandbox Code Playgroud)

然后有堆分配的变量.这意味着存在从标准库(alloc在C或newC++中)请求内存的库调用.该存储器保留到程序执行结束.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)或完全基于堆栈(第四).


dan*_*ben 7

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

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

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

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

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