+---------+
| 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.
+---------+
+-----------+ 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
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.
}
#include <stdlib.h>
#include <stdio.h>
int main()
{
int stackValue = 0;
int *addressOnStack = &stackValue;
int *addressOnHeap = malloc(sizeof(int));
if (addressOnStack > addressOnHeap)
{
puts("The stack is above the heap.");
}
else
{
puts("The heap is above the stack.");
}
}
Windows 的内存模型不同,因为它使用的代码类型不同。Windows 使用 PE 文件格式,这使得代码采用与位置相关的格式。也就是说,代码取决于代码在虚拟内存中的确切加载位置。PE 规范中有一个标志,它告诉操作系统在程序运行时库或可执行文件在内存中的确切位置。如果一个程序或库不能在它的首选地址加载,Windows 加载程序必须 重新定位库/可执行文件——基本上,它移动依赖于位置的代码来指向新的位置——这不需要查找表,也不能被利用,因为没有查找表可以覆盖。不幸的是,这需要在 Windows 加载程序中非常复杂的实现,并且如果需要重新定位映像,那么开销相当大。大型商业软件包经常修改它们的库,以便在不同的地址启动,以避免重定基; Windows 自己的库(例如 ntdll.dll、 kernel32.dll、 psapi.dll 等——默认情况下都有不同的启动地址)就是这样做的
在 X86体系结构中,CPU 使用寄存器执行操作。堆栈只是出于方便的原因而使用。您可以在调用子例程或系统函数之前将寄存器的内容保存到堆栈中,然后将它们加载回来,以继续您离开时的操作。(您可以不使用堆栈手动操作它,但它是一个经常使用的函数,因此它具有 CPU 支持)。但是你可以做几乎任何事情没有堆栈在电脑上。
例如整数乘法:
MUL BX
将 AX 寄存器与 BX 寄存器相乘。(结果将是 DX 和 AX,包含较高位的 DX)。
基于堆栈的机器(如 JAVA VM)使用堆栈进行基本操作:
DMUL
这将从堆栈顶部弹出两个值并乘以 tem,然后将结果推回堆栈。堆栈对于这种机器是必不可少的。
一些更高级的编程语言(比如 C 和 Pascal)使用这种稍后的方法将参数传递给函数: 参数按从左到右的顺序被推到堆栈中,然后由函数体弹出,返回值被推回。(这是编译器制造商做出的选择,而且有点滥用 X86使用堆栈的方式)。
堆
堆是仅存在于编译器领域中的另一个概念。它消除了处理变量后面的内存的痛苦,但它不是 CPU 或操作系统的函数,它只是管理内存块的一种选择,这个内存块是由操作系统提供的。如果你愿意的话,你可以多来几次。
访问系统资源
操作系统有一个公共接口,您可以通过这个接口访问它的功能。在 DOS 中,参数在 CPU 的寄存器中传递。Windows 使用堆栈为 OS 函数(WindowsAPI)传递参数。