编译器如何在内存中布置代码

时间:2013-09-30 18:42:23

标签: memory assembly compiler-construction operating-system virtual-memory

好的我有一点菜鸟问题。

所以我很熟悉堆栈包含子程序调用,而堆包含可变长度数据结构,全局静态变量分配给永久存储器位置。

但这一切如何在较低的理论水平上发挥作用?

编译器是否只是假设它从地址0到地址无穷大有一个完整的内存区域?然后开始分配东西?

它在哪里布局指令,堆栈和堆?在存储区域的顶部,存储区域的结束?

那么它如何与虚拟内存一起使用?虚拟内存对程序是透明的吗?

对于bajilion的问题很抱歉,但是我正在学习编程语言结构,并且它一直在指这些区域,我想在更实际的层面上理解它们。

非常感谢!

3 个答案:

答案 0 :(得分:6)

全面的解释可能超出了本论坛的范围。整篇文章都致力于这一主题。但是,在简单的层面上,你可以这样看待它。

编译器不会将代码布局在内存中。它确实假设它拥有整个内存区域。编译器生成目标文件,其中目标文件中的符号通常从偏移量0开始。

链接器负责将目标文件拉到一起,将符号链接到链接对象中的新偏移位置并生成可执行文件格式。

链接器也没有在内存中布置代码。它将代码和数据打包成通常标记为.text的部分用于可执行代码指令,将.data打包为全局变量和字符串常量等部分。 (还有其他部分用于不同的目的)链接器可以提供操作系统加载器的提示,在哪里重定位符号,但加载器不必强制。

操作系统加载程序解析可执行文件并决定代码和数据在内存中的布局位置。其位置完全取决于操作系统。通常,堆栈位于比程序指令和数据更高的内存区域,并向下增长。

每个程序都是编译/链接的,假设它有自己的整个地址空间。这就是虚拟内存的用武之地。它对程序完全透明,完全由操作系统管理。

虚拟内存的范围通常从地址0到平台支持的最大地址(不是无穷大)。该虚拟地址空间由操作系统划分为内核可寻址空间和用户可寻址空间。假设在假设的32位操作系统上,0x80000000以上的地址是为操作系统保留的,下面的地址供程序使用。如果程序试图访问此分区上方的内存,它将被中止。

操作系统可能决定堆栈从最高可寻址用户存储器开始,并随着位于更低地址的程序代码而增长。

堆的位置通常由您构建程序的运行时库管理。它可以从您的程序代码和数据之后的下一个可用地址开始。

答案 1 :(得分:2)

这是一个涉及很多主题的广泛问题。

假设典型的编译器 - >汇编程序 - >链接器工具链。编译器不知道很多,它只是编码堆栈相关的东西,不关心堆栈的数量或位置,这是堆栈的目的/美丽,不在乎。编译器生成汇编程序,汇编程序被组装成一个对象,然后链接器接受一些flavor或命令行参数的信息链接器脚本,告诉它内存空间的详细信息,当你

gcc hello.c -o hello

你的binutils安装有一个默认的链接器脚本,它适合你的目标(windows,mac,linux,无论你运行的是什么)。该脚本包含有关程序空间开始位置的信息,然后从那里知道从哪里开始堆(在文本,数据和bss之后)。堆栈指针可能由该链接描述文件设置和/或操作系统以其他方式管理它。这定义了你的堆栈。

对于具有mmu的操作系统,这是你的windows和linux以及mac和bsd笔记本电脑或台式计算机所具有的,然后是每个程序编译,假设它有自己的地址空间从0x0000开始,这并不意味着该程序链接开始运行在0x0000,它取决于操作系统操作系统规则是什么,一些从0x8000开始,例如。

对于类似于应用程序的桌面,从程序的角度来看,它有点单个线性地址空间,你可能首先使用.text然后是.data或.bss,然后在所有这些之后堆将在某些时候对齐那。然而,它被设置的堆栈通常是高并且向下运行但是可以是处理器和操作系统特定的。该堆栈通常位于世界的程序视图中,是其内存的顶部。

虚拟内存对应用程序通常不知道或不关心虚拟内存的所有这些都是不可见的。如果应用程序获取指令或进行数据传输,它将通过操作系统配置的硬件,并在虚拟和物理之间进行转换。如果mmu指示故障,意味着空间尚未映射到物理地址,那么有时可能是故意的,然后再使用术语“虚拟存储器”。这个第二个定义操作系统然后可以例如占用其他一些内存,你或其他人,将其移动到硬盘,例如,将其他块标记为不在那里,然后将你的块标记为有一些ram然后让你执行的不知道你被某个公羊打断了,你不知道你必须从别人那里拿走。你的设计应用程序不想知道任何这个,它只是想运行,操作系统负责管理物理内存和mmu,它给你一个虚拟(零基础)地址空间......

如果你要进行一些裸机编程,首先没有mmu的东西,然后是微控制器,qemu,raspberry pi,beaglebone等,你可以用编译器,链接器脚本和配置来弄脏你的手一个mmu。我会使用一个手臂或mips这不是x86,只是为了让你的生活更轻松,整体大图直接翻译成目标。

答案 2 :(得分:1)

取决于。

如果您正在编译一个必须从头开始的引导程序,您可以假设您已经拥有了整个内存。

另一方面,如果你正在编译一个应用程序,你可以假设你已经拥有了整个内存。

细微差别在于,在第一种情况下,您拥有自己的所有物理记忆。作为一个引导程序,RAM中还没有别的东西。在第二种情况下,内存中有一个操作系统,但它会(通常)为你设置虚拟内存,这样你就可以拥有整个地址空间。但是,你仍然需要向操作系统询问实际内存。

后者确实意味着操作系统强加了一些规则。例如。操作系统非常想知道程序的第一条指令在哪里。一个简单的规则可能是您的程序始终从地址0开始,因此C编译器可以将int main()放在那里。操作系统通常想知道堆栈的位置,但这已经是一个更灵活的规则。就“堆”而言,操作系统真的无所顾忌。