谁负责C ++中的堆栈和堆?

时间:2015-12-31 21:32:07

标签: c++ c compiler-construction stack heap

这不是任何一个目的是什么的问题。相反,这是一个问题,谁或什么负责堆栈和堆的发明?

这些C ++编译器的发明是什么?

操作系统是否指定RAM中指定为“堆栈”和“堆”的内存部分?

我很确定它们没有内置于硬件中,但我可能错了。

此外,编译器是否负责生成汇编代码,指定哪些本地或函数数据将存储在堆栈与CPU寄存器中?

6 个答案:

答案 0 :(得分:8)

这篇文章是关于的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,即0x7c0sp设置为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 ++代码。没有任何操作系统。看看它是如何工作的,可能有助于理解堆栈和堆的责任在哪里。

堆最容易解释。每次newdelete一个对象(或调用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中引用堆栈或堆(nearfar指针[最高可达4GB] huge指针)。

随着时间的推移,随着硬件技术的发展,物理内存类型之间的区别消失了 - 所有内存(除了CPU缓存等)在物理上都是同一类型的内存,在给定的机器上(或芯片组介导和提供)统一的内存接口,即使存在不同内存的物理库)。为了管理进程,操作系统(unix,32位窗口等)倾向于保留类似于堆栈的内存部分(执行程序所需的基本内存)并使用配额强制限制。对于现代操作系统,这通常被称为堆栈,但(由于虚拟内存管理等技术的奇迹)被分配给进程而不是系统范围,而堆指的是其他可用的内存。程序可以动态分配的过程。