10

好的,我有一个菜鸟学生的问题。

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

但是这一切是如何在一个不太理论的层面上运作的呢?

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

它在哪里布置指令、堆栈和堆?在内存区域的顶部,内存区域的末尾?

那么这如何与虚拟内存一起工作?虚拟内存对程序是透明的吗?

很抱歉有一个问题,但我正在学习编程语言结构,它一直在引用这些区域,我想在更实际的层面上理解它们。

提前非常感谢!

4

3 回答 3

11

全面的解释可能超出了本论坛的范围。整篇文章都致力于这个主题。但是,从简单的角度来看,您可以这样看待它。

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

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

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

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

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

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

操作系统可以决定堆栈从最高可寻址用户内存开始,并随着位于低得多的地址的程序代码向下增长。

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

于 2013-09-30T19:17:35.347 回答
2

这是一个广泛的开放性问题,有很多主题。

假设典型的编译器 -> 汇编器 -> 链接器工具链。编译器并不知道很多,它只是对堆栈相关的东西进行编码,不关心堆栈的大小或位置,那是堆栈的目的/美,不在乎。编译器生成汇编器,将汇编器组装成一个对象,然后链接器获取一些风格的链接器脚本或命令行参数,告诉它内存空间的详细信息,当你

gcc hello.c -o hello

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

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

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

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

如果您要进行一些裸机编程,首先没有 mmu 的东西,然后使用微控制器、qemu、raspberry pi、beaglebone 等,您可以使用编译器、链接器脚本和配置 mmu。我会为此而不是 x86 使用 arm 或 mips,只是为了让您的生活更轻松,整体大图都直接跨目标转换。

于 2013-09-30T19:23:55.713 回答
1

这取决于。

如果您正在编译必须从头开始的引导加载程序,您可以假设您已经为自己获得了整个内存。

另一方面,如果你正在编译一个应用程序,你可以假设你已经为自己获得了全部内存。

细微的区别在于,在第一种情况下,您拥有自己的所有物理内存。作为引导加载程序,RAM 中还没有其他内容。在第二种情况下,内存中有一个操作系统,但它会(通常)为您设置虚拟内存,以便您看起来拥有自己的整个地址空间。不过,通常你仍然需要向操作系统询问实际内存。

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

于 2013-09-30T19:13:13.100 回答