如果可以在堆栈上更高效地完成所有工作,我们为什么需要堆?


24

这实际上与我昨天问的问题有关,为什么今天使用的应用程序同时需要堆栈(以及为什么不能只使用堆而不同时使用堆,为了拥有简单的&唯一标准)。

但是,许多响应表明堆栈是不可替代的,这是因为与尝试分配/引用堆相比要快数百(或数千)倍。我知道,如果不使用Heap,动态存储分配就会出现问题,但是没有办法解决这一问题,或者没有办法在Stack上进行改进以使其能够处理动态内存分配吗?


4
上一个问题的两个摘录:“最重要的缺点是空间有限,因此在其中保留大型对象或试图将其用于寿命长的对象都是不好的主意”,并且“堆栈非常有效。遵循LIFO(后进先出)规则的数据管理结构”。
卡斯卡贝尔2011年

2
您的前提是错误的-并非所有事情都能在堆栈上更高效地完成。这与您收到的答案没有矛盾- 可以在堆栈上更快地完成堆栈上的操作。
Ingo

...假设您的硬件具有堆栈或相对堆栈寻址。
里奇·梅尔顿

3
我相信。我说去做
JeffO

Answers:


25

堆栈的问题在于,除非内存位于堆栈顶部,否则您无法“释放”内存。例如,假设您分配了3种大小不同的东西:

a = allocate(2000000); // 2000000 bytes
b = allocate(1);
c = allocate(5000000);

堆栈将a在底部,b中间和c顶部。如果我们要免费,这将成为问题b

free(b); // b is not on top! We have to wait until c is freed!

解决方法是先移动所有数据,b然后再移动a。这可行,但是在这种情况下将需要500万个副本-这比堆慢得多。

这就是为什么我们有一个堆。尽管分配的速度可能比堆栈慢(O(log n)vs O(1)),但与堆栈的内存O(log n)相比,堆可以更快地释放任意位置的内存-O(n)


4
与此相关的是,当您要求Facebook删除其内容时,Facebook不会从其磁盘中删除内容,它只是删除了指向它的指针。显然,尝试进行碎片整理或试图在磁盘上找到等效间隙的开销以其写入数据的速度太耗时,因此,他们只会在磁盘的高水位添加所有内容。
Paul Tomblin,

好吧,facebook的磁盘可以看作是堆。而且我很确定他们为这些磁盘提供了某种垃圾回收。
deadalnix

2
@deadalnix实际上,这是使用大堆栈而不是通常用于大量内存的堆的示例。Facebook是一个特例。数据添加的速度比删除数据的速度快得多,因此释放不会对增长率产生重大影响-您可以在设计中故意包括内存泄漏以获取O(1)分配。
汤姆·克拉克森,

7
@PaulTomblin主要原因FB不删除的内容是,让他们可以挖掘它的利润...
quant_dev

5
Facebook doesn't remove content from its disk when you ask it to remove it, it just removes the pointer to it-本质上就是在任何操作系统上普通删除文件时发生的情况。
罗伯特·哈维

5

堆栈是按线程的,堆是整个进程的

如果有100个线程将我处理的所有处理工作项目放入队列中,那么我到底在哪里分配工作项目,以便100个线程中的任何一个都能看到它们?

也有其他类型的记忆

例如,内存映射文件,共享内存,I / O映射(内核模式)。在这些情况下,效率论点是没有根据的。


4

堆栈是一种LIFO(后进先出)结构,其顶部保留了一个引用指针(通常由硬件支持)。话虽如此,您要尝试在堆栈而不是堆上分配的任何内容都必须是每个函数的局部变量,位于该堆栈的顶部。因此,反对栈的主要原因是您的main()例程需要预先分配程序使用的所有数据结构(打算在程序的整个过程中使用)作为分配的所有数据结构当这些函数调用返回并且其框架或激活记录从堆栈中弹出时,这些函数调用内的函数最终将被删除。


3
LIFO,不是FIFO。
Pubby 2011年

有时也称为FILO;)
oenone

3

堆栈对于遵循后进先出(LIFO)规则的内存分配非常有用,也就是说,您可以按照分配的完全相反的顺序释放内存。LIFO是一种非常常见的,也许是最常见的内存分配模式。但这不是唯一的模式,甚至不是唯一的通用模式。为了编写可以解决各种问题的高效程序,我们必须考虑到不太常见的模式,即使这意味着更复杂的基础结构也是如此。

如果我能获得段落的所有元数据:您是一个初学者,作为一个初学者,您重视简单性和黑白规则。但是,作为初学者,您只能窥视计算机程序必须解决的一系列问题和约束。您正在进入一项经过75年积极开发的技术。询问为什么事情是这样并没有错,但是答案通常是:“是的,我们在50年前尝试了简单,直接的方法,结果证明整个课程的效果都不理想问题,所以我们不得不做一些更复杂的事情”。随着技术的进步,简单性通常必须让位于效率和灵活性。


0

再举一个例子,闭包。如果堆栈弹出,则可能会丢失闭包的激活记录。因此,如果要保留该匿名函数及其数据,则必须将其存储在运行时堆栈之外的其他位置。


0

分配堆存储的许多其他原因。

如果要将对象的地址传递回调用程序,则不应传递堆栈变量的地址,因为堆栈存储将被重用,并可能被下一个调用的函数覆盖。您需要使用malloc()获得所需的存储,以确保不会被后续函数的调用覆盖。

您可以将堆栈项的地址从函数传递到调用的函数,因为可以保证它们将一直存在,直到您的程序“ returns()”为止。但是,一旦函数返回,所有堆栈存储就可以使用了。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.