这确实取决于系统,但带有virtual memory 的现代操作系统倾向于加载它们的进程映像并分配内存,如下所示:
+---------+
| stack | function-local variables, return addresses, return values, etc.
| | often grows downward, commonly accessed via "push" and "pop" (but can be
| | accessed randomly, as well; disassemble a program to see)
+---------+
| shared | mapped shared libraries (C libraries, math libs, etc.)
| libs |
+---------+
| hole | unused memory allocated between the heap and stack "chunks", spans the
| | difference between your max and min memory, minus the other totals
+---------+
| heap | dynamic, random-access storage, allocated with 'malloc' and the like.
+---------+
| bss | Uninitialized global variables; must be in read-write memory area
+---------+
| data | data segment, for globals and static variables that are initialized
| | (can further be split up into read-only and read-write areas, with
| | read-only areas being stored elsewhere in ROM on some systems)
+---------+
| text | program code, this is the actual executable code that is running.
+---------+
这是许多常见虚拟内存系统上的通用进程地址空间。 “洞”是你总内存的大小,减去所有其他区域占用的空间;这为堆增长提供了大量空间。这也是“虚拟的”,这意味着它通过转换表映射到您的实际内存,并且可能实际存储在实际内存中的任何位置。这样做是为了保护一个进程不访问另一个进程的内存,并使每个进程都认为它运行在一个完整的系统上。
请注意,例如堆栈和堆的位置在某些系统上的顺序可能不同(有关 Win32 的更多详细信息,请参阅下面的Billy O'Neal's answer)。
其他系统可能非常不同。例如,DOS 在real mode 中运行,它在运行程序时的内存分配看起来就大不相同了:
+-----------+ top of memory
| extended | above the high memory area, and up to your total memory; needed drivers to
| | be able to access it.
+-----------+ 0x110000
| high | just over 1MB->1MB+64KB, used by 286s and above.
+-----------+ 0x100000
| upper | upper memory area, from 640kb->1MB, had mapped memory for video devices, the
| | DOS "transient" area, etc. some was often free, and could be used for drivers
+-----------+ 0xA0000
| USER PROC | user process address space, from the end of DOS up to 640KB
+-----------+
|command.com| DOS command interpreter
+-----------+
| DOS | DOS permanent area, kept as small as possible, provided routines for display,
| kernel | *basic* hardware access, etc.
+-----------+ 0x600
| BIOS data | BIOS data area, contained simple hardware descriptions, etc.
+-----------+ 0x400
| interrupt | the interrupt vector table, starting from 0 and going to 1k, contained
| vector | the addresses of routines called when interrupts occurred. e.g.
| table | interrupt 0x21 checked the address at 0x21*4 and far-jumped to that
| | location to service the interrupt.
+-----------+ 0x0
您可以看到 DOS 允许直接访问操作系统内存,没有任何保护,这意味着用户空间程序通常可以直接访问或覆盖他们喜欢的任何内容。
然而,在进程地址空间中,程序往往看起来相似,只是它们被描述为代码段、数据段、堆、堆栈段等,并且映射方式略有不同。但大部分一般区域仍然存在。
在将程序和必要的共享库加载到内存中并将程序的各个部分分配到正确的区域后,操作系统开始执行您的进程,无论其主要方法所在的位置,您的程序都会从那里接管,进行系统调用在需要时根据需要。
不同的系统(无论是嵌入式系统)可能具有非常不同的架构,例如无堆栈系统、哈佛架构系统(代码和数据保存在单独的物理内存中)、实际上将 BSS 保存在只读内存中的系统(最初由程序员设置)等。但这是一般要点。
你说:
我也知道一个计算机程序使用两种内存:栈和堆,它们也是计算机主内存的一部分。
“堆栈”和“堆”只是抽象概念,而不是(必然)物理上不同的“种类”内存。
stack 只是一个后进先出的数据结构。在 x86 架构中,它实际上可以通过使用距离末尾的偏移量来随机寻址,但最常见的功能是 PUSH 和 POP 分别用于添加和删除项目。它通常用于函数局部变量(所谓的“自动存储”)、函数参数、返回地址等(更多见下文)
"heap" 只是一块内存的昵称,可以按需分配,并且是随机寻址的(意味着您可以直接访问其中的任何位置)。它通常用于您在运行时分配的数据结构(在 C++ 中,使用 new 和 delete,以及 malloc 和 C 中的朋友等)。
在 x86 架构上,堆栈和堆都物理地驻留在您的系统内存 (RAM) 中,并通过虚拟内存分配映射到如上所述的进程地址空间。
registers(仍然在 x86 上)物理上驻留在处理器内部(与 RAM 相对),并由处理器从 TEXT 区域加载(也可以从内存中的其他位置或其他位置加载,具体取决于在实际执行的 CPU 指令上)。它们本质上只是非常小、非常快的片上内存位置,可用于多种不同用途。
寄存器布局高度依赖于架构(实际上,寄存器、指令集和内存布局/设计正是“架构”的含义),因此我不会对其进行扩展,但建议您参加汇编语言课程以更好地理解它们。
你的问题:
堆栈在什么时候用于执行指令?指令从 RAM 到堆栈,再到寄存器?
堆栈(在拥有和使用它们的系统/语言中)最常这样使用:
int mul( int x, int y ) {
return x * y; // this stores the result of MULtiplying the two variables
// from the stack into the return value address previously
// allocated, then issues a RET, which resets the stack frame
// based on the arg list, and returns to the address set by
// the CALLer.
}
int main() {
int x = 2, y = 3; // these variables are stored on the stack
mul( x, y ); // this pushes y onto the stack, then x, then a return address,
// allocates space on the stack for a return value,
// then issues an assembly CALL instruction.
}
像这样写一个简单的程序,然后把它编译成汇编(gcc -S foo.c,如果你可以访问 GCC),看看。组装很容易理解。您可以看到堆栈用于函数局部变量,以及调用函数,存储它们的参数和返回值。这也是为什么当您执行以下操作时:
f( g( h( i ) ) );
所有这些都被依次调用。它实际上是在构建一堆函数调用及其参数,执行它们,然后在它回退(或向上;)时将它们弹出。但是,如上所述,堆栈(在 x86 上)实际上驻留在您的进程内存空间(在虚拟内存中),因此可以直接对其进行操作;它不是执行期间的单独步骤(或至少与流程正交)。
仅供参考,上面是C calling convention,也被 C++ 使用。其他语言/系统可能会以不同的顺序将参数推送到堆栈上,有些语言/平台甚至不使用堆栈,而是以不同的方式进行。
另外请注意,这些不是实际执行的 C 代码行。编译器已将它们转换为可执行文件中的机器语言指令。 然后(通常)将它们从 TEXT 区域复制到 CPU 管道中,然后复制到 CPU 寄存器中,然后从那里执行。 [这是不正确的。请参阅下面的Ben Voigt's correction。]