C++中的内存分配区域(栈、堆和静态区)

10

我知道在C++中有三个内存区域:堆栈、堆和为静态分配特性而分配的区域。我有两个问题:

  1. 为什么堆比栈慢得多?它应该只是一个额外的间接层吗?

  2. 为静态“特性”(变量、函数、类)分配的内存区域是否比堆提供更快的性能?


3
我对其中一些观点不确定,无法回答,但是这篇文章是相关必读的。 - axiom
3
请注意,这一切都是特定于平台的,编程语言本身并没有涉及栈、堆等内容。 - Oliver Charlesworth
在C++中,内存分配区域也不是“存在”的。 - Lightness Races in Orbit
1
为什么堆比栈慢那么多?它只是多了一个间接层,所以肯定应该快一点吧?“只是”?这会增加100%的延迟! - Lightness Races in Orbit
这个链接可能会有所帮助:https://dev59.com/jXRC5IYBdhLWcg3wG9Nb - Fei Jiang
有没有一些好的书籍来描述操作系统(最好是Linux)如何处理内存管理? - user997112
6个回答

14
首先需要注意的是,正确的术语是自动而不是栈,动态而不是堆。另一个是,在C++11中现在有四种内存类型而不是三种。C++11将线程本地内存添加到混合中。
自动内存因为大多数机器上都是使用调用堆栈实现的,所以很快。只需通过正确的数量来调整堆栈指针,就可以分配内存了。动态内存在底层需要进行更多的工作。必要的内存可能没有附加到进程,而使其发生需要通过操作系统。即使可用内存,动态内存管理工具仍然必须找到并标记为使用中。
静态内存在编译和链接过程中“分配”。当您在某个源文件中定义静态变量时,编译后的代码包含特殊指令,以便链接器为该变量保留空间。编译器还将您的C/C++代码转换为机器代码。链接器将所有这些不同的数据和代码块组合在一起,并解析地址以形成可执行二进制图像。运行程序时,该二进制图像被加载到(虚拟)内存中。该静态变量的内存在程序开始执行时就已经存在。
就性能而言,最好不要过于担心性能。虽然静态内存很快,但有很多缺点。您最不想做的事情就是使所有数据都变成静态的。

尽管最后一段可能会让人感到困惑,但编译和链接确实不会在目标系统上分配内存。我很感激您使用引号来表明这不是真正的“分配”,但是否更清晰地讨论静态内存如何成为进程内存的一部分,以及可执行映像与程序本身一起读入目标系统内存时如何加载呢? - Lightness Races in Orbit
@David Hammen- 关于最后一点,您对结果性能有什么看法吗?(同时感谢您的术语建议)。 - user997112
通常的术语是“堆栈”和“堆”。就像有些人试图避免使用这些词一样,可以尝试避免对“对象”一词的误解,但这将是徒劳无功的:那些无法理解一个词在不同上下文中可能有不同含义的人,将会遇到其他困难——这只是微小地转移了问题,但为大多数其他人创造了一个新的更大的问题。 - Cheers and hth. - Alf
自动内存之所以快速,是因为在大多数操作系统中它是使用调用栈实现的。按照定义,自动内存是调用栈的一部分(例如,“堆栈展开”这个术语就是指这个栈,无论其实现方式如何)。我认为你指的是机器硬件支持的调用栈。 - Cheers and hth. - Alf
@LightnessRacesinOrbit - 我更新了我的答案以反映您的评论。谢谢。 - David Hammen

3

1) 为什么堆比栈慢得多?它只是一个额外的间接层,不应该太慢吧?

因为在栈上分配内存意味着将堆栈指针sp 增加1 N 个字节。以这种方式更改sp 足够快。对于堆,则需要执行许多操作,例如查找空闲插槽或请求操作系统获得内存,这些都是昂贵的操作。

1.或减少,取决于栈的增长方向!

2) 分配给静态“特征”(变量,函数,类)的内存区域能够提供比堆更快的性能吗?

如果您指的是static变量,则是的,它们比堆更快,因为它们是静态分配的,并且存在于程序结束之前。但是,在此上下文中不能比较为函数分配的内存(问题对我来说不完全有意义)。


我不知道任何向上增长的系统堆栈。它们都是向下增长的。 - Cheers and hth. - Alf
2
@Cheersandhth.-Alf:我刚在脑海中构想了一个实现C++且系统栈向上增长的系统。 - Lightness Races in Orbit

3
比较堆栈、堆和静态分配区域的速度时,有两种不同的速度需要比较。
首先是访问速度。这对于三个区域来说是可以比较的,尽管本地(堆栈分配)变量可能会稍微占优势,因为它们更容易被编译器缓存在CPU寄存器中。否则,它基本上只是内存访问。
其次是分配速度。这是巨大的差异所在。
静态分配对象在程序启动时(或库加载时间,当它们驻留在动态加载库中)获得其内存保留,因此它们的分配时间对于程序来说是无法测量的短暂的。
堆栈分配对象也很便宜,因为它们采用可预测的分配模式(最后分配==最先取消分配),编译器可以轻松考虑这一点。例如,如果一个应用程序有多个线程,则还会有多个堆栈(每个堆栈为一个线程保留),因此线程不必互相竞争以访问堆栈内存。
堆分配对象是困难的,因为堆用于所有不适合前面组的内容。而且,通常整个应用程序只有一个堆,因此需要线程同步来从堆中分配内存。并且,由于分配/取消分配模式相当随机,必须采取措施确保不会因为堆的碎片而“丢失”太多堆内存。这一切都需要一些时间,在进行分配或取消分配时支付。

有没有一本描述你所说内容的好书,针对Linux? 不确定这是否属于“Linux内核”主题? - user997112
@user997112:我不知道有没有,但这并不能说明什么。;-) - Bart van Ingen Schenau

2
虽然"堆比较慢"这种说法过于笼统,但与堆分配相关的某些方面确实比堆要慢。不同于栈,栈无法出现碎片化,因为你不能从栈中间删除物品,而堆则会受到碎片化问题的困扰。你的程序可以以任意顺序取消分配对象,因此在堆中会形成“空洞”。当你请求更多内存时,分配器必须搜索适当的“空洞”来满足你的请求。
此外,计算机硬件对基于栈的操作进行了高度优化。最后,在自动存储(“堆栈”的官方名称)中分配的对象由于靠近程序访问的其他本地变量而有更高的缓存概率。
至于静态存储,它仅适用于数据成员:静态函数和静态成员函数只是重新使用关键字而不是重新使用其含义。静态存储中的对象仅被分配一次,因此不存在碎片化的原因。
一旦完成分配,三种存储(静态、动态和自动)中的对象访问速度几乎是相同的。

1
我不认为性能是主观的(例如,因人而异)。它确实因不同的实现而异,但对于给定的实现,它可以客观地进行测量,并且对于类似的实现,推理和形式化模型可以提供性能的客观指标。 - user395760
@delnan 当有人说“堆栈比较慢”时,他或她指的是某个特定方面,例如分配速度、释放速度、访问速度等。您可以测量和比较每个单独方面的速度。然而,不能说“堆栈比堆慢”,暗示使用堆的程序会比使用堆栈的类似程序明显更慢。这就是我所说的“堆栈比较慢”陈述的主观性。 - Sergey Kalinichenko
啊,你说得有道理。但对我来说,这并不属于主观范畴,而是“过于宽泛,无法具有实际意义”的范畴。 - user395760
@dasblinkenlight,如果您的数据在堆中发生了碎片化,这可能会增加页面错误、缓存未命中以及TLB未命中的几率? - user997112
碎片化会减缓分配速度,而不是访问速度。当有大量未分配的块可供选择时,通常需要更长时间来找到合适的内存块。一旦分配,内存的行为与平常一样:您不一定会得到更多的缓存未命中(尽管由于附近的无关访问而获得“免费搭车”的可能性肯定会降低)。 - Sergey Kalinichenko

1

1) 这不是间接性问题,而是内存管理的问题。在堆栈上分配内存只涉及将堆栈指针向上移动。而在堆上分配则需要寻找正确大小的内存段,并考虑碎片。

2) 静态内存在程序进入主入口点之前被分配,因此没有真正的运行时开销。对已经分配的内存的访问速度实际上并不取决于内存分配的位置。


-1

栈比堆快得多的一个原因是由于局部性原理。存储在栈中的数据位于连续位置,这意味着如果引用一个变量,则其相邻信息会自动带入缓存。存储在堆上的数据(除了动态数组)没有这个优势。


堆并不更快,它更慢! - Alok Save
@LightnessRacesinOrbit:是的,没错,所以我添加了一条评论,让OP可以进行更正。我怀疑OP已经排队等待编辑了。这个踩并不是我给的! - Alok Save
@LightnessRacesinOrbit 是我。我对“局部性引用是使堆栈更快的原因”的推理有异议。即使你考虑了一秒钟,这个推理也站不住脚,因为我们没有100字节的缓存之类的东西。如果缓存如此之小,可能一个更简单的布局会导致问题,因为一个节点的子节点在一个远离的位置,问题会在较低的层级上放大。所以我会等待更正并撤销投票。 - axiom
1
-1 其余的都是无关紧要的。栈也在RAM中,缓存不关心某个内存区域是“栈”的一部分还是“堆”或其他任何东西。可能有一个潜在的差异:栈由于使用频繁,几乎保证大部分时间都在缓存中,而动态分配的内存(例如,大多数链表)则有更多潜在的机会破坏缓存行为。但是,任何熟悉低级优化的人都会告诉您,可以完全可能和可行地布置堆分配的数据,使其与缓存良好配合。 - user395760
@delnan,我从未提到堆不在RAM中。我所说的是,在堆栈上访问数据很可能会将其他有用的数据带入缓存,而在堆中则不太可能。这一点有什么反对意见吗? - Arani
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接