这不是任何一个目的是什么的问题。相反,这是一个问题,谁或什么负责堆栈和堆的发明?
这些C ++编译器的发明是什么?
操作系统是否指定RAM中指定为“堆栈”和“堆”的内存部分?
我很确定它们没有内置于硬件中,但我可能错了。
此外,编译器是否负责生成汇编代码,指定哪些本地或函数数据将存储在堆栈与CPU寄存器中?
答案 0 :(得分:8)
这篇文章是关于x86的32位Linux。我不了解其他架构/操作系统。
这些C ++编译器的发明是什么? [OS]是否指定了内存 RAM中指定为“堆栈”和“堆”的部分?
一个程序有different sections,其中一个是.data
部分。动态内存分配通常使用brk
系统调用(sbrk
构建在其上)来实现。 man brk
说以下内容:
brk()
和sbrk()
更改了程序中断的位置, 它定义了进程数据段的结束(即, 程序中断是结束后的第一个位置 未初始化的数据段)。增加程序休息时间 为进程分配内存的效果;减少 打破释放内存。
这意味着“堆”或“免费商店”实际上是.data
部分。
正如@kfx在对此答案的评论中所说,没有标准规定必须使用malloc
来实现brk
。使用mmap
的实现也是可能的。
来自man mmap
:
mmap()
函数应在地址空间之间建立映射
进程和内存对象。
这基本上意味着文件(Unix意识形态:“一切都是文件”)被映射到内存中。
堆栈也位于.data
部分并向下增长。从技术上讲,x86使您能够定义向下增长的堆栈段,但Linux不使用此功能。不知道为什么。
我很确定它们不是内置于硬件中但我可以 错。
不,他们不是。段在OS的运行时设置,而不是存储在硬件中。
以下内容来自Linux 4.2。
当MBR跳转到引导加载程序并且引导程序已执行时,执行此操作(路径为/arch/x86/boot/header.S
):
# Normalize the start address
ljmp $BOOTSEG, $start2
start2:
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
xorw %sp, %sp
这里所有段寄存器都初始化为$BOOTSEG
,即0x7c0
。 sp
设置为0x00
。没有esp
,因为我们仍处于实模式!
在完成初始化之后,执行跳转到真实内核。段寄存器的设置如下:
movw $__BOOT_DS, %cx
movw $__BOOT_TSS, %di
movl %cr0, %edx
orb $X86_CR0_PE, %dl # Protected mode
movl %edx, %cr0
# Transition to 32-bit mode
.byte 0x66, 0xea # ljmpl opcode
2: .long in_pm32 # offset
.word __BOOT_CS # segment
ENDPROC(protected_mode_jump)
.code32
.section ".text32","ax"
GLOBAL(in_pm32)
# Set up data segments for flat 32-bit mode
movl %ecx, %ds
movl %ecx, %es
movl %ecx, %fs
movl %ecx, %gs
movl %ecx, %ss
段寄存器再次设置为cs
的内容。
此外,编译器负责生成汇编代码 指定哪些本地或函数数据将存储在堆栈中 CPU寄存器?
是。对于函数调用,有不同的calling conventions:有些将它们的参数推送到堆栈,有些将它们移动到寄存器中。
局部变量也可以用寄存器或堆栈实现
未经优化的C编译器将参数压入堆栈,调用函数,然后将其弹出(“stdcall”调用约定)。
它们将堆栈用于局部变量,并与ebp
寄存器结合使用。
答案 1 :(得分:7)
谁或什么负责发明堆栈和堆?
就发明堆栈和堆而言,您可以更好地搜索网页。这些概念已存在数十年。
这些C ++编译器的发明是什么?
也许发明在这里是错误的术语。它们是数据结构。编译器和OS(如果存在)负责组织和利用存储器。
操作系统是否指定RAM中指定为“堆栈”和“堆”的内存部分?
这是特定于操作系统的,可能因操作系统而异。有些操作系统会保留堆栈和堆区域,有些则不会。
在我正在进行的嵌入式系统中,有两个堆区:1)链接器中指定的区域,2)分配给OS的一部分内存。这两个区域都设置为零,所以我们没有任何堆。
堆栈区域由在初始化C语言运行时库之前运行的初始化代码设置。 RTL代码也可以创建一些堆栈区域。我们的RTOS还创建堆栈区域(每个任务一个)。
因此,没有一个名为堆栈的区域。某些平台根本不使用堆栈概念(特别是那些内存容量受到严格限制的概念)。
我很确定它们没有内置于硬件中,但我可能错了。
取决于硬件。简单且廉价的硬件仅分配RAM区域(读/写存储器)。更复杂和昂贵的硬件可以为堆栈,堆,可执行文件和数据分配单独的区域。常量可以放入ROM(只读存储器,如Flash)中。没有支持所有内容的单一配置或单配置。台式计算机与较小的嵌入式系统不同,是 animals 。
此外,编译器是否负责生成汇编代码,指定哪些本地或函数数据将存储在堆栈与CPU寄存器中?
任务可以在链接器或编译器中,也可以在两者中 许多编译器工具链都使用堆栈和CPU寄存器。许多变量和数据可以在堆栈,寄存器,RAM或ROM中。编译器旨在充分利用平台的资源,包括内存和寄存器。
研究的一个好例子是编译器生成的汇编语言。另请查看链接器指令文件。寄存器或堆栈存储器的使用如此依赖于数据结构(和类型),它们对于不同的功能可能是不同的。另一个因素是可用的内存量和种类。如果处理器可用的寄存器很少,则编译器可以使用堆栈传递变量。较大的数据(不适合寄存器)可以在堆栈上传递或传递给数据的指针。这里列举的选项和组合太多了。
为了使C和C ++语言非常可移植,许多概念被委托给实现(编译器/工具链)。其中两个概念通常称为堆栈和堆。 C和C ++语言标准使用简单模型作为语言环境。此外,还有诸如“托管”和“半托管”之类的术语,表示平台支持语言要求的程度。 堆栈和堆是平台不是必需的数据结构,以支持这些语言。它们确实有助于提高执行效率。
如果支持堆栈和堆,则它们的位置和管理是实现的责任(工具链)。编译器可以自由使用它自己的内存管理功能或OS(如果存在)。堆栈和堆的管理可能需要硬件支持(例如虚拟内存管理或分页;以及防护)。堆栈不需要向堆增长。不需要堆栈以正方向增长。这些都取决于实现(工具链),他们可以实现和定位堆栈,无论他们喜欢什么。 注意:很可能,它们不会将变量放在只读内存中,也不会将堆栈放在内存容量之外。
答案 2 :(得分:4)
这是实现定义的(C / C ++不知道任何硬件)
答案 3 :(得分:2)
操作系统是否指定RAM中指定的内存部分" stack"和"堆"?
是的,对于每个程序,操作系统都会为数据分配:
我很确定它们没有内置于硬件中
硬件与内存布局无关。它提供内存,操作系统完成其余的工作。但是,在x86 / x64和许多其他CPU架构上,虽然CPU仍然没有强加堆栈,但它提供了专用的注册表和指令来管理堆栈,只有堆栈,因为它是一个非常重要的功能。
此外,编译器是否生成汇编代码,指定将在堆栈中存储哪些信息与存储在CPU寄存器中的信息?
这些信息由链接器而不是编译器生成,它们存储在可执行文件头中,而不是存储在汇编代码中。
编辑:事实上我已经回答了这个问题'操作系统是否指定了RAM中指定的内存部分" stack"和"堆"?'。但是我已经读过"其中"而不是"什么"。对不起。
答案 4 :(得分:2)
正如@chux所提到的,你可以编写在'裸机上运行的C或C ++代码。没有任何操作系统。看看它是如何工作的,可能有助于理解堆栈和堆的责任在哪里。
堆最容易解释。每次new
或delete
一个对象(或调用malloc/free
)时,编译器都会生成调用分配/释放函数的代码。 (通常,new
通常最终会调用malloc
)。因此,编译器的可解析性只是为了生成对这两个函数的调用。
为堆分配的初始空间由程序运行时由OS确定,或者由程序员指定(作为裸机代码),作为链接器设置的一部分。
那些功能在哪里,他们做了什么?通常,它们是运行时库的一部分,它们与已编译的代码相关联(并且它们通常用C或C ++编写)。关于裸机'他们直接管理堆的系统 - 就CPU而言,这只是一个内存区域。 CPU没有任何堆的概念。如果涉及操作系统,这些功能通常只调用操作系统提供的堆管理功能。有时,它是一个混合体,因为库可能会管理它自己的堆,但是如果它耗尽,可能会调用操作系统来获取更多的内存块。
堆栈不同。几乎每个CPU都有硬件堆栈的概念(尽管堆栈只存在于普通内存中)。当您调用函数并分配基于堆栈的变量时,编译器会生成用于操作“堆栈指针”的代码。 (相应的特殊CPU寄存器)。使用多线程代码,每个线程都有自己的堆栈,操作系统的多任务内核负责为每个任务切换到适当的堆栈。
为了进一步混淆你,在多线程系统中,每个线程的堆栈空间通常是从堆中分配的。
答案 5 :(得分:1)
你错了。堆栈和堆不是源自C或C ++。它们起源于早期机器中的物理内存架构。
我将为x86硬件提供粗略的故事(我确定有很多具体内容,我缺少,掩盖等等)。我认为与其他硬件系列有类似的发展,但我不熟悉那些。
早期的PC总内存为640K或更少,其中可以加载程序(来自容量为几百K的软盘驱动器)并执行。这个记忆区域后来被称为"常规记忆"。当早期的PC开发出来时,人们相信这种内存量可能比以往任何时候都要多。
当时,向前迈出了一大步,使用扩展卡将物理内存从640K增加到1MB。有许多不兼容的变种。最广泛使用的是基于由微软,英特尔和其他几家公司联合开发的扩展内存规范。这被命名为扩展内存。
然后是286 CPU,支持保护模式,能够处理超过1MB的内存 - 高达64MB。其他物理内存库(物理上独立的芯片组)用于安装额外的内存。在最初的实现中,这个内存比传统内存慢,但是更大的数量更便宜。它被各种名称引用,例如扩展内存(XMS规范)或高内存(以访问它的其中一个设备驱动程序的名称命名)。更高版本的XMS规范允许寻址高达4GB的内存(即32位内存)。
常规和扩展内存对于程序运行是必要的 - 如果没有足够的可用于加载可执行代码,程序就无法执行。这被称为堆栈 - 只是传统内存,扩展内存和由其他(非英特尔,非微软)供应商的驱动程序管理的类似内存的统称。它也由使用LIFO堆栈方法的程序管理(例如,当一个函数调用时,上下文放在堆栈数据结构上,并在函数返回时弹出)。在像C这样的语言中,局部和全局变量被分配在"堆栈"上。由于可用的数量很少(任何系统都不超过1MB),因此这个内存总是非常宝贵。
扩展内存最初用于程序存储数据的额外内存需求。正如我上面所说的那样,这个内存最初速度较慢,但数量较多则更便宜。它是在各种设备驱动程序(himem.sys等)的帮助下管理的,其中每个设备驱动程序都提供了一个独特且不兼容的API。无论用什么设备驱动程序管理它,堆最初都是所有这些额外内存的统称。当时支持动态内存分配(使用C,malloc()
,free()
等)的语言/库通常使用扩展内存(如果内存不是物理上可用的话有一些后备)并处理与各种可能的设备驱动程序通信的详细信息。一些支持带有指针(或类似结构)的语言的编译器使用不同的指针类型来分别在早期版本的XMS和更高版本的XMS中引用堆栈或堆(near
和far
指针[最高可达4GB] huge
指针)。
随着时间的推移,随着硬件技术的发展,物理内存类型之间的区别消失了 - 所有内存(除了CPU缓存等)在物理上都是同一类型的内存,在给定的机器上(或芯片组介导和提供)统一的内存接口,即使存在不同内存的物理库)。为了管理进程,操作系统(unix,32位窗口等)倾向于保留类似于堆栈的内存部分(执行程序所需的基本内存)并使用配额强制限制。对于现代操作系统,这通常被称为堆栈,但(由于虚拟内存管理等技术的奇迹)被分配给进程而不是系统范围,而堆指的是其他可用的内存。程序可以动态分配的过程。