堆栈和堆在哪里?

memory-management stack language-agnostic heap dynamic-memory-allocation 蓝兰鹿 | 2020-02-02 19:35:06


编程语言书籍解释说,值类型是在堆栈上创建的,引用类型是在堆上创建的,而没有解释这两种类型是什么。我还没有读到对此的明确解释。我知道什么是堆栈。但是,
它们在哪里和什么(物理上在真正的计算机内存中)?
它们在多大程度上受操作系统或语言运行时控制?
他们的范围是什么?
是什么决定了它们的大小?
是什么让你更快?


本站提供【Linux疑难问题解决】50元起 ,【爬虫开发】200元起。欢迎咨询QQ:376667689 点击这里给我发消息



25 回答


5848


堆栈是作为执行线程的暂存空间而预留的内存。调用函数时,在堆栈顶部为局部变量和某些记帐数据保留一个块。当该函数返回时,块将变为未使用的块,并且可以在下次调用函数时使用。堆栈总是按后进先出的顺序保留;最近保留的块总是要释放的下一个块。这使得跟踪堆栈变得非常简单;从堆栈中释放块只不过是调整一个指针。
堆是为动态分配而预留的内存。与堆栈不同,没有强制模式来分配和释放堆中的块;您可以随时分配块并释放它。这使得跟踪堆的哪些部分在任何给定时间被分配或释放变得更加复杂;有许多自定义堆分配器可用于针对不同的使用模式优化堆性能。
每个线程都有一个堆栈,而应用程序通常只有一个堆(尽管有多个堆并不少见堆用于不同类型的分配)。
直接回答您的问题:
操作系统或语言运行时在多大程度上控制它们?
创建线程时,操作系统为每个系统级线程分配堆栈。通常,语言运行库调用操作系统来为应用程序分配堆。
它们的作用域是什么?BS>将堆栈连接到线程,因此当线程退出堆栈时被回收。堆通常由运行时在应用程序启动时分配,并且在应用程序(技术进程)退出时被回收。
堆栈的大小在创建线程时设置。堆的大小是在应用程序启动时设置的,但可以随着空间的需要而增加(分配器从操作系统请求更多内存)。
什么使堆更快?
堆栈的速度更快,因为访问模式使得从中分配和释放内存变得很简单(指针/整数只是递增或递减),而堆在分配或释放中有更复杂的簿记。此外,堆栈中的每个字节往往被非常频繁地重用,这意味着它往往被映射到处理器的缓存,从而使它非常快。堆的另一个性能冲击是,堆主要是一个全局资源,通常必须是多线程安全的,即每个分配和释放都需要与程序中的“所有”其他堆访问同步。
一个清晰的演示:
图像源:vikashazrati.wordpress.com

2020-02-02 19:35:18
人斬

2293


堆栈:
像堆一样存储在计算机RAM中。
堆栈上创建的变量将超出作用域并自动释放。
与堆上的变量相比,分配要快得多。
使用实际的堆栈数据结构实现。
存储本地数据,返回地址,用于参数传递。
当使用太多堆栈(主要是无限递归或太深递归)时,可能会出现堆栈溢出,“非常大的分配”。
在堆栈上创建的数据可以不用指针使用。
如果你知道在编译时你需要分配多少数据,并且它不是太大,你会使用栈。
通常在程序启动时已经确定了最大的大小。
堆:
就像堆栈一样存储在计算机RAM中。C++中,堆上的变量必须手动销毁,并且永远不会超出范围。该数据为[ 2 ] d,< >代码> < /PRE>,
 >代码> [ 1 ]  < /Prime>,或代码>免费< /代码> 。BR>与堆栈上的变量相比,分配较慢。
按需分配一个数据块供程序使用。堆上创建的数据将由指针指向,并分别使用
new
malloc
进行分配。
如果请求分配的缓冲区太大,则可能会发生分配失败。
如果不清楚运行时需要多少数据或需要多少数据,则将使用堆分配大量数据。
负责内存泄漏。
示例:
int foo()
{
char *pBuffer; // bool b = true; // Allocated on the stack.
if(b)
{
//Create 500 bytes on the stack
char buffer[500];
//Create 500 bytes on the heap
pBuffer = new char[500];
}//}//

2020-02-02 19:35:18
艾斯BLUE

1347


最重要的一点是,堆和堆栈是内存分配方式的通用术语。它们可以以多种不同的方式实现,这些术语适用于基本概念。
在一堆项中,项按照它们在其中的放置顺序一个接一个地放置,并且您只能移除最上面的项(而不推翻整个项)。
堆栈的简单性是,您不需要维护包含已分配内存的每个部分的记录;您需要的唯一状态信息是指向堆栈末尾的单个指针。要分配和取消分配,只需增加和减少单个指针。注意:堆栈有时可以实现为从内存段的顶部开始向下扩展,而不是向上扩展。
在堆栈中,项的放置方式没有特定的顺序。由于没有明确的“top”项,您可以按任何顺序进入并移除项。
堆分配需要维护已分配和未分配内存的完整记录,以及一些开销维护,以减少碎片,找到足够大的连续内存段以适应请求的大小,等等。内存可以在任何时候释放,留下空闲空间。有时,内存分配器将执行维护任务,例如通过移动已分配的内存对内存进行碎片整理,或者在运行时进行垃圾收集-当内存不再在作用域中时进行标识并将其释放。
这些图像应该能够很好地描述在堆栈和堆中分配和释放内存的两种方式。好极了!
它们在多大程度上受操作系统或语言运行时控制?
如前所述,堆和堆栈是通用术语,可以通过多种方式实现。计算机程序通常有一个称为调用堆栈的堆栈,该堆栈存储与当前函数相关的信息,例如指向从哪个函数调用它的指针,以及任何局部变量。因为函数调用其他函数,然后返回,所以堆栈会增大和缩小以保存调用堆栈下函数的信息。一个程序对它没有真正的运行时控制;它是由编程语言、操作系统甚至系统架构决定的。
堆是一个通用术语,用于动态和随机分配的任何内存,即无序内存。内存通常由操作系统分配,应用程序调用API函数来进行分配。在管理动态分配的内存(通常由操作系统处理)时,需要一定的开销。
它们的作用域是什么?
调用堆栈是一个低级的概念,在编程意义上它与“scope”无关。如果反汇编某些代码,您将看到对堆栈部分的相对指针样式引用,但就更高级别的语言而言,该语言强加了自己的范围规则。不过,堆栈的一个重要方面是,一旦函数返回,该函数的任何本地内容都会立即从堆栈中释放出来。考虑到编程语言的工作方式,它的工作方式与您预期的一样。堆在一起,也很难定义。作用域是操作系统公开的任何内容,但是您的编程语言可能会添加关于“作用域”在您的应用程序中是什么的规则。处理器体系结构和操作系统使用虚拟寻址,处理器将其转换为物理地址,并且存在页面错误等。它们跟踪哪些页面属于哪些应用程序。不过,您不必担心这个问题,因为您只需使用编程语言用于分配和释放内存的任何方法,并检查错误(如果分配/释放因任何原因失败)。
是什么决定了每个内存的大小?
同样,它取决于语言、编译器、操作系统和体系结构。堆栈通常是预先分配的,因为根据定义,它必须是连续内存(在最后一段中有更多内容)。语言编译器或操作系统决定其大小。您不会在堆栈上存储大量数据,因此它将足够大,永远不应完全使用,除非出现不需要的无休止递归(因此,“堆栈溢出”)或其他不寻常的编程决策。
堆是可以动态分配的任何东西的通用术语。根据你看它的方式,它的大小是不断变化的。在现代的处理器和操作系统中,它的工作方式是非常抽象的,所以您通常不需要太担心它的深层工作方式,除了(在它允许您使用的语言中)您不能使用尚未分配的内存或已释放的内存。
什么使它更快?
堆栈更快,因为所有可用内存总是连续的。不需要维护所有空闲内存段的列表,只需一个指向当前堆栈顶部的指针。为此,编译器通常将此指针存储在一个特殊的快速寄存器中。更重要的是,堆栈上的后续操作通常集中在内存的非常近的区域内,这在非常低的级别上有利于处理器在片缓存上进行优化。

2020-02-02 19:35:18
烈火青蛙

714


(我已经把这个答案从另一个或多或少是这个问题的重复部分移走了。)
您的问题的答案是特定于实现的,并且可能因编译器和处理器体系结构而异。不过,这里有一个简单的解释。
堆栈和堆都是从底层操作系统分配的内存区域(通常是根据需要映射到物理内存的虚拟内存)。
在多线程环境中,每个线程都有自己完全独立的堆栈,但它们将共享堆。并发访问必须在堆上进行控制,而在堆栈上是不可能的。

堆包含已使用和可用块的链接列表。堆上的新分配(通过
new
malloc
)通过从一个空闲块创建合适的块来满足。这需要更新堆上的块列表。关于堆上块的元信息也存储在堆上,通常存储在每个块前面的一个小区域中。
随着堆的增长new块通常从较低的地址分配到较高的地址。因此,可以将堆看作是随着内存分配而增大的内存块堆。如果堆太小,无法进行分配,则通常可以通过从底层操作系统获取更多内存来增加大小。
分配和解除分配许多小块可能会使堆处于一种状态,即在使用的块之间散布许多小的可用块。分配大块的请求可能失败,因为没有一个空闲块大到足以满足分配请求,即使空闲块的组合大小可能足够大。这称为堆碎片。
当释放与空闲块相邻的已用块时,可以将新的空闲块与相邻的空闲块合并,以创建更大的空闲块,从而有效地减少堆碎片。

堆栈通常与名为堆栈指针。最初,堆栈指针指向堆栈的顶部(堆栈上的最高地址)。
CPU有特殊指令,用于将值推送到堆栈上并从堆栈中弹出。每次推送都将值存储在堆栈指针的当前位置,并减少堆栈指针。弹出窗口检索堆栈指针指向的值,然后增加堆栈指针(不要被向堆栈中添加值会减少堆栈指针而删除值会增加堆栈指针这一事实所混淆)。记住,堆栈会增长到底部)。存储和检索的值是CPU寄存器的值。
调用函数时,CPU使用特殊指令来推送当前指令指针,即堆栈上执行的代码的地址。然后,CPU通过将
指令指针设置为所调用函数的地址,跳转到该函数。稍后,当函数返回时,旧的指令指针将从堆栈中弹出,并在调用函数后在代码处继续执行。
当输入函数时,堆栈指针将减小,以便在堆栈上为本地(自动)变量分配更多空间。如果函数有一个本地32位变量,则在堆栈上留出4个字节。当函数返回时,堆栈指针移回以释放分配的区域。
如果函数有参数,则在调用函数之前将这些参数推送到堆栈上。然后,函数中的代码可以从当前堆栈指针向上导航堆栈,以定位这些值。
嵌套函数调用的工作就像一个符咒。每一个新的调用都会为本地变量分配函数参数、返回地址和空间,这些激活记录可以为嵌套的调用进行堆叠,并在函数返回时以正确的方式展开。
因为堆栈是一个有限的内存块,通过调用太多嵌套函数和/或为局部变量分配太多空间,可以导致堆栈溢出。通常,用于堆栈的内存区域的设置方式是,在堆栈底部(最低地址)以下写入将触发CPU中的陷阱或异常。这种异常情况可以被运行时捕获并转换为某种堆栈溢出异常。

是否可以在堆上分配函数而不是堆栈?
不,函数(即本地或自动变量)的激活记录是在堆栈上分配的,堆栈不仅用于存储这些变量,还用于跟踪嵌套的函数调用。
堆的管理方式实际上取决于运行时环境。C使用代码> [2 ] ,C++使用代码>新< /代码> /Prime>,但许多其他语言都有垃圾收集。在没有足够空间的情况下增长堆并不太难,因为它可以在处理堆的库调用中实现。但是,增加堆栈通常是不可能的,因为只有在太晚的时候才发现堆栈溢出;关闭执行线程是唯一可行的选择。

2020-02-02 19:35:18
黑桐幹也

398


在下面的C代码中,
public void Method1()
{
int i = 4;
int y = 2;
class1 cls1 = new class1();
}

这里是如何管理内存的
Local Variables
方法,只要函数调用进入堆栈,它就只需要持续一段时间。堆用于那些我们不知道其生命周期的变量,但是我们希望它们能持续一段时间。在大多数语言中,在编译时知道一个变量有多大是非常重要的,如果我们想把它存储在堆栈中的话。
对象(随着我们更新它们的大小而变化)会堆在堆上,因为我们不知道在创建时它们会持续多长时间。在许多语言中,堆被垃圾收集以查找不再具有任何引用的对象(例如cls1对象)。
在Java中,大多数对象直接进入堆。在C/C++的语言中,当你不处理指针时,结构和类通常可以保留在堆栈上。
这里可以找到更多的信息:
堆栈和堆内存分配之间的差异:TimMurPHy.org
,这里:
在堆栈和堆上创建对象。.NET概念:堆栈、堆、值类型、引用类型、装箱和解包-代码项目
但请注意它可能包含一些不准确之处。

2020-02-02 19:35:18
恋夏的冰

203


堆栈
当调用函数时,该函数的参数加上一些其他开销会放在堆栈上。有些信息(例如返回时要去哪里)也存储在那里。
当您在函数中声明一个变量时,该变量也会在堆栈上分配。
取消分配堆栈非常简单,因为总是按与分配顺序相反的顺序取消分配。当您输入函数时,添加堆栈材料,在退出时删除相应的数据。这意味着,除非调用大量调用其他函数的函数(或创建递归解决方案),否则您往往会停留在堆栈的一个小区域内。

堆是用于动态放置所创建数据的通用名称。如果你不知道你的程序将要创建多少艘宇宙飞船,你可能会使用new(或malloc或等效的)操作符来创建每艘宇宙飞船。这种分配将持续一段时间,因此我们很可能会以不同于我们创建它们的顺序释放它们。
因此,堆要复杂得多,因为最终会有未使用的内存区域与内存碎片交织在一起。找到所需大小的空闲内存是一个困难的问题。这就是为什么应该避免堆(尽管堆仍然经常使用)。
堆栈和堆的实现通常都是由运行时/OS来完成的。通常,性能关键的游戏和其他应用程序会创建自己的内存解决方案,从堆中获取大量内存,然后在内部释放,以避免依赖操作系统获得内存。
只有当你的内存使用与正常情况完全不同时,这才是可行的,即对于游戏来说,你在一个巨大的操作中加载一个级别,而在另一个巨大的操作中可以把所有的内容都扔掉。
内存中的物理位置
这与你的想法不太相关,因为一种叫做虚拟内存的技术让你的程序认为你可以访问物理数据所在的某个地址(甚至在硬盘上!)。随着调用树的深入,堆栈的地址将按顺序递增。堆的地址是不可预测的(即特定于实现的地址),坦率地说并不重要。

2020-02-02 19:35:18
Charlex

189


为了澄清,这个答案有错误的信息(托马斯在评论后修正了他的答案,酷:)。其他的答案只是避免解释静态分配的含义。因此,我将解释三种主要的分配形式,以及它们通常如何与下面的堆、堆栈和数据段相关。我也将在C/C++和Python中展示一些例子来帮助人们理解。不要以为很多人这样做只是因为“静态”听起来很像“堆栈”。它们实际上既不存在于堆栈中,也不存在于堆中。是所谓的数据段的一部分。
但是,通常最好考虑“作用域”和“生存期”,而不是“堆栈”和“堆”。
作用域指代码的哪些部分可以访问变量。一般来说,我们认为本地作用域(只能由当前函数访问)和全局作用域(可以在任何地方访问)虽然作用域会变得复杂得多。
生存期是指在程序执行期间分配和释放变量的时间。通常我们会考虑静态分配(变量将在程序的整个过程中持续存在,这对于在多个函数调用中存储相同的信息很有用)与自动分配(变量只在对函数的单个调用中持续存在,使其在存储仅在函数期间使用且一旦完成就可以丢弃的信息时有用,而不是动态分配(变量的持续时间是在运行时定义的,而不是像静态或自动那样的编译时)。
尽管大多数编译器和解释器在使用堆栈时类似地实现此行为,堆等,编译器有时可能会打破这些惯例,如果它想只要行为是正确的。例如,由于优化,局部变量可能只存在于寄存器中,或者完全被删除,即使大部分的局部变量存在于堆栈中。正如在一些注释中指出的,您可以自由地实现一个编译器,它甚至不使用堆栈或堆,而是使用其他一些存储机制(很少这样做,因为堆栈和堆对这一点很好)。
我将提供一些简单的带注释的C代码来说明所有这些。最好的学习方法是在调试器下运行程序并观察其行为。如果您更喜欢阅读python,请跳到答案的末尾:)
// Statically allocated in the data segment when the program/DLL is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in the code
int someGlobalVariable;
// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in this particular code file
static int someStaticVariable;
// "someArgument" is allocated on the stack each time MyFunction is called
// "someArgument" is deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
void MyFunction(int someArgument) {
// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed only within MyFunction()
static int someLocalStaticVariable;
// Allocated on the stack each time MyFunction is called
// Deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
int someLocalVariable;
// A *pointer* is allocated on the stack each time MyFunction is called
// This pointer is deallocated when MyFunction returns
// scope - the pointer can be accessed only within MyFunction()
int* someDynamicVariable;
// This line causes space for an integer to be allocated in the heap
// when this line is executed. Note this is not at the beginning of
// the call to MyFunction(), like the automatic variables
// scope - only code within MyFunction() can access this space
// *through this particular variable*.
// However, if you pass the address somewhere else, that code
// can access it too
someDynamicVariable = new int;

// This line deallocates the space for the integer in the heap.
// If we did not write it, the memory would be "leaked".
// Note a fundamental difference between the stack and heap
// the heap must be managed. The stack is managed for us.
delete someDynamicVariable;
// In other cases, instead of deallocating this heap space you
// might store the address somewhere more permanent to use later.
// Some languages even take care of deallocation for you... but
// always it needs to be taken care of at runtime by some mechanism.
// When the function returns, someArgument, someLocalVariable
// and the pointer someDynamicVariable are deallocated.
// The space pointed to by someDynamicVariable was already
// deallocated prior to returning.
return;
}
// Note that someGlobalVariable, someStaticVariable and
// someLocalStaticVariable continue to exist, and are not
// deallocated until the program exits.

区分lifetime和scope非常重要的一个特别令人痛心的例子是,一个变量可以有本地作用域,但可以有静态生存期,例如上面的代码示例中的“someLocalStaticVariable”。这样的变量会使我们常见但非正式的命名习惯非常混乱。例如,当我们说“本地”时,我们通常是指“本地范围的自动分配变量”,当我们说“全局范围的静态分配变量”时,我们通常是指“全局范围的静态分配变量”。不幸的是,当谈到“文件范围的静态分配变量”时,很多人只是说呵呵???"
C/C++中的一些语法选择加剧了这个问题,例如许多人认为全局变量不是静态的,因为下面的语法是这样的。
< PRE> >代码>(1)< /代码> > BR>注意,在上面的声明中放置关键字“static”可以防止VAR2具有全局范围。然而,全局var1具有静态分配。这不是直觉!因此,在描述作用域时,我尽量不使用“static”一词,而是说“file”或“file limited”作用域之类的词。然而,许多人使用短语“static”或“static scope”来描述只能从一个代码文件访问的变量。在生命周期的上下文中,“静态”总是指变量在程序启动时被分配并在程序退出时被释放。他们不是。例如,下面的Python示例演示了所有三种类型的分配(在解释语言中可能存在一些细微的差异,我在这里将不涉及这些差异)。
from datetime import datetime
class Animal:
_FavoriteFood = 'Undefined' # _FavoriteFood is statically allocated
def PetAnimal(self):
curTime = datetime.time(datetime.now()) # curTime is automatically allocatedion
print("Thank you for petting me. But it's " + str(curTime) + ", you should feed me. My favorite food is " + self._FavoriteFood)
class Cat(Animal):
_FavoriteFood = 'tuna' # Note since we override, Cat class has its own statically allocated _FavoriteFood variable, different from Animal's
class Dog(Animal):
_FavoriteFood = 'steak' # Likewise, the Dog class gets its own static variable. Important to note - this one static variable is shared among all instances of Dog, hence it is not dynamic!

if __name__ == "__main__":
whiskers = Cat() # Dynamically allocated
fido = Dog() # Dynamically allocated
rinTinTin = Dog() # Dynamically allocated
whiskers.PetAnimal()
fido.PetAnimal()
rinTinTin.PetAnimal()
Dog._FavoriteFood = 'milkbones'
whiskers.PetAnimal()
fido.PetAnimal()
rinTinTin.PetAnimal()
# Output is:
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is milkbones
# Thank you for petting me. But it's 13:05:02.256000, you should feed me. My favorite food is milkbones

2020-02-02 19:35:18
バカ

166


其他人已经很好地回答了宽泛的笔触,所以我将介绍一些细节。
Stack and heap不必是单数的。通常情况下,如果一个进程中有多个线程,则会有多个堆栈。在这种情况下,每个线程都有自己的堆栈。您也可以有多个堆,例如,某些DLL配置会导致从不同堆分配不同的DLL,这就是为什么释放由不同库分配的内存通常是一个坏主意。
在C中,您可以通过使用alloca(在堆栈上分配)来获得可变长度分配的好处,如与alloc相反,alloc在堆上进行分配。这个内存在return语句中无法保存,但它对于临时缓冲区很有用。
在不太使用的Windows上生成一个巨大的临时缓冲区是不可用的。这是因为编译器将生成一个堆栈探测循环,每次调用函数时都会调用堆栈循环,以确保堆栈的存在(因为Windows在您的堆栈末尾使用单个保护页来检测何时需要堆栈。如果访问堆栈末尾的内存超过一页,则会崩溃)。示例:
void myfunction()
{
char big[10000000];
// Do something that only uses for first 1K of big 99% of the time.
}

2020-02-02 19:35:18
游魂

134


其他人已经直接回答了您的问题,但是在试图理解堆栈和堆时,我认为考虑传统UNIX进程的内存布局(没有线程和基于
mmap()
的分配器)是有帮助的。内存管理词汇表网页提供了此内存布局的图表。
堆栈和堆通常位于进程虚拟地址空间的两端。堆栈在访问时会自动增长,最大可达到内核设置的大小(可以使用
setrlimit(RLIMIT_STACK, ...)
进行调整)。当内存分配器调用
brk()
sbrk()
系统调用,将更多页的物理内存映射到进程的虚拟地址空间时,堆会增长。
在没有虚拟内存的系统中,例如一些嵌入式系统,通常采用相同的基本布局,只是堆栈和堆的大小是固定的。然而,在其他嵌入式系统(例如基于Microchip PIC微控制器的嵌入式系统)中,程序堆栈是一个单独的内存块,不能由数据移动指令寻址,只能通过程序流指令(调用、返回等)间接修改或读取。其他体系结构(如英特尔安腾处理器)有多个堆栈。从这个意义上讲,堆栈是CPU体系结构的一个元素。

2020-02-02 19:35:18
缘来壹梦

113


堆栈是内存的一部分,可以通过几个键汇编语言指令进行操作,例如“pop”(从堆栈中移除并返回值)和“push”(将值推送到堆栈中),也可以调用(调用子例程-将地址推送到堆栈中)和返回(从子例程返回-将地址弹出从堆里跳下来。它是堆栈指针寄存器下面的内存区域,可以根据需要进行设置。堆栈还用于向子例程传递参数,以及在调用子例程之前保留寄存器中的值。
堆是操作系统提供给应用程序的内存的一部分,通常通过类似malloc的系统调用。在现代操作系统中,内存是一组只有调用进程才能访问的页。
堆栈的大小在运行时确定,通常在程序启动后不会增长。在C程序中,堆栈必须足够大,以容纳每个函数中声明的每个变量。堆将根据需要动态增长,但操作系统最终会调用堆(堆的增长通常会超过malloc请求的值,因此至少将来的一些malloc不需要返回内核以获得更多内存。这种行为通常是可定制的)
因为在启动程序之前已经分配了堆栈,所以在使用堆栈之前不需要malloc,所以这是一个小优势。实际上,在具有虚拟内存子系统的现代操作系统中,很难预测什么是快的,什么是慢的,因为如何实现页面以及页面存储在哪里是一个实现细节。

2020-02-02 19:35:18
某因幡

113


什么是堆栈?
堆栈是一堆对象,通常是整齐排列的对象。

计算体系结构中的堆栈是以后进先出的方式添加或删除数据的内存区域。
在多线程应用程序中,每个线程都有自己的堆栈。

什么是堆?
堆是杂乱地堆积起来的东西的集合。

在计算体系结构中,堆是由操作系统或内存管理器库自动管理的动态分配内存区域。
堆上的内存在程序执行期间定期分配、释放和调整大小,这可能导致一个称为碎片的问题。
当内存对象之间分配的空间太小,无法容纳其他内存对象时,会发生碎片。
最终的结果是堆空间的一个百分比,不能用于进一步的内存分配。

在多线程应用程序中,每个线程都有自己的堆栈。但是,所有不同的线程将共享堆。
因为不同的线程在多线程应用程序中共享堆,这也意味着线程之间必须有一些协调,以便它们不会试图同时访问和操作堆中的同一块内存。

哪个更快-堆栈还是堆?为什么呢?

堆栈比堆快得多。
这是因为堆栈上的内存分配方式。
在堆栈上分配内存就像向上移动堆栈指针一样简单。

对于刚开始编程的人来说,使用堆栈可能是个好主意,因为它更容易。
因为堆栈很小,所以当您确切地知道需要多少内存来存储数据,或者知道数据的大小非常小时,您应该使用它。
当你知道你的数据需要很多内存,或者你只是不确定你需要多少内存(比如动态数组)时,最好使用堆。
Java内存模型
堆栈是存储局部变量(包括方法参数)的内存区域。当涉及到对象变量时,它们仅仅是对堆中实际对象的引用(指针)。
每次实例化对象时,都会留出一块堆内存来保存该对象的数据(状态)。由于对象可以包含其他对象,因此某些数据实际上可以保存对这些嵌套对象的引用。

2020-02-02 19:35:18
用户昵称已经存在!

112


我想很多人在这件事上给了你最正确的答案。
然而,有一个被忽略的细节是,“堆”实际上应该被称为“免费商店”。这种区别的原因是,最初的空闲存储是用一种称为“二项式堆”的数据结构实现的。因此,从malloc()/free()的早期实现中分配是从堆中分配的。然而,在当今时代,大多数免费商店都是用非常精细的数据结构实现的,这些数据结构不是二项堆。

2020-02-02 19:35:18
平野绫

90


你可以用这个堆栈做一些有趣的事情。例如,你有像alloca这样的函数(假设你可以忽略大量关于它使用的警告),这是malloc的一种形式,专门使用堆栈而不是堆作为内存。
也就是说,基于堆栈的内存错误是我所经历过的最糟糕的一些错误。如果使用堆内存,并且超出了所分配块的界限,则有相当大的机会触发段错误。(不是100%:您的块可能偶然地与以前分配的另一个块相邻。)但是由于堆栈上创建的变量总是彼此相邻的,因此写出界可以更改另一个变量的值。我了解到,每当我觉得我的程序不再遵循逻辑法则时,很可能是缓冲区溢出。

2020-02-02 19:35:18
神隐能力者14

88


简单地说,堆栈是创建局部变量的地方。此外,每次调用子程序时,程序计数器(指向下一条机器指令的指针)和任何重要寄存器,有时参数会被推送到堆栈上。然后,子例程中的任何局部变量都被推送到堆栈中(并从堆栈中使用)。当子例程完成时,这些东西都会从堆栈中弹出。PC和register数据在弹出的时候得到并放回原来的位置,这样你的程序就可以愉快地运行了。
堆是内存动态分配的区域(显式的“new”或“allocate”调用)。它是一种特殊的数据结构,可以跟踪大小不一的内存块及其分配状态。
在“经典”系统中,RAM的布局是这样的:堆栈指针从内存底部开始,堆指针从顶部开始,它们朝着彼此生长。如果它们重叠,说明内存不足。不过,这并不适用于现代多线程操作系统。每个线程都必须有自己的堆栈,这些堆栈可以动态创建。

2020-02-02 19:35:18
呆呆的睫毛

81


从WikiAnwser.
堆栈
当一个函数或一个方法调用另一个依次调用另一个函数等的函数时,所有这些函数的执行都保持挂起状态,直到最后一个函数返回其值。
这个挂起的函数调用链就是堆栈,因为堆栈中的元素(函数调用)依赖于每个其他。
堆栈在异常处理和线程执行中很重要。

堆只是程序用来存储变量的内存。
堆的元素(变量)彼此之间没有依赖关系,总是可以随时随机访问。

2020-02-02 19:35:18
特异型峰岛由宇

53


堆栈
非常快速的访问
不必显式地取消分配变量
CPU可以有效地管理空间,内存不会碎片化
仅局部变量
堆栈大小限制(取决于操作系统)
变量不能调整大小

变量可以全局访问
内存大小没有限制
(相对而言)访问速度较慢
没有保证有效使用空间,随着时间的推移,随着内存块的分配,内存可能会碎片化,然后释放
您必须管理内存(您负责分配和释放变量)
可以使用realloc()调整变量大小

2020-02-02 19:35:18
羽狱狱

45


好吧,简而言之,他们的意思是有秩序的和没有秩序的。。。!
堆栈:在堆栈项中,东西会相互重叠,意味着处理起来会更快、更高效!...
所以总是有一个索引来指向特定的项目,也会处理得更快,项目之间也有关系!…
堆:没有顺序,处理速度会变慢,值被弄乱,没有特定的顺序或索引。。。这是随机的,它们之间没有关系。。。因此,执行和使用时间可能会有所不同……
我还创建了下面的图像,以显示它们的外观:

2020-02-02 19:35:18
琥珀

45


简而言之,堆栈用于静态内存分配,堆用于动态内存分配,两者都存储在计算机的RAM中。
详细地说,堆栈是一种“后进先出”的数据结构,由CPU非常紧密地管理和优化。每次函数声明一个新变量时,它都被“推”到堆栈上。然后,每当函数退出时,由该函数推送到堆栈上的所有变量都被释放(也就是说,它们被删除)。一旦释放堆栈变量,该内存区域就可用于其他堆栈变量。
使用堆栈存储变量的优点是,内存是为您管理的。你不必手动分配内存,也不必在不再需要时释放内存。更重要的是,由于CPU有效地组织堆栈内存,读取和写入堆栈变量的速度非常快。
这里可以找到更多信息。

堆是计算机内存中的一个区域,不是为您自动管理的,也不是由CPU严格管理的。它是一个更自由的内存浮动区域(并且更大)。要在堆上分配内存,必须使用malloc()或calloc(),这是内置的C函数。一旦在堆上分配了内存,就要负责在不再需要该内存时使用free()来释放该内存。
如果不这样做,程序将出现所谓的内存泄漏。也就是说,堆中的内存仍将被保留(其他进程将无法使用)。正如我们将在调试部分看到的,有一个名为Valgrind的工具可以帮助您检测内存泄漏。
与堆栈不同,堆对可变大小没有大小限制(除了计算机明显的物理限制)。堆内存的读取和写入速度稍慢,因为必须使用指针来访问堆上的内存。我们将很快讨论指针。
与堆栈不同,堆上创建的变量可由程序中的任何函数访问。堆变量在作用域中基本上是全局的。
这里可以找到更多。
堆栈上分配的变量直接存储在内存中,对该内存的访问非常快,并且在编译程序时处理其分配。当一个函数或一个方法调用另一个函数,而该函数又反过来调用另一个函数等时,所有这些函数的执行将保持挂起状态,直到最后一个函数返回其值为止。堆栈总是按后进先出顺序保留,最近保留的块总是要释放的下一个块。这使得跟踪堆栈变得非常简单,从堆栈中释放一个块只不过是调整一个指针。
在堆上分配的变量在运行时分配了它们的内存,访问这个内存稍微慢一些,但堆大小仅受虚拟内存大小的限制。堆中的元素彼此之间没有依赖关系,并且总是可以随时随机访问。您可以在任何时候分配一个块,并在任何时候释放它。这使得跟踪堆的哪些部分在任何给定的时间被分配或释放变得更加复杂。
如果您确切知道在编译之前需要分配多少数据,并且数据量不太大,则可以使用堆栈。如果不知道运行时需要多少数据或需要分配大量数据,则可以使用堆。
在多线程情况下,每个线程都有自己完全独立的堆栈,但它们将共享堆。堆栈是线程特定的,堆是应用程序特定的。在异常处理和线程执行中,堆栈是需要考虑的重要因素。
每个线程都有一个堆栈,而应用程序通常只有一个堆(尽管对于不同类型的分配,有多个堆并不少见)。
在运行时,如果应用程序需要更多堆,它可以从可用内存中分配内存,并且如果堆栈需要内存,它可以从为应用程序分配的可用内存中分配内存。
甚至,这里和这里都提供了更多详细信息。
现在回答您的问题。
它们在多大程度上受操作系统或语言运行时控制?
创建线程时,操作系统为每个系统级线程分配堆栈。通常,操作系统由语言运行库调用,为应用程序分配堆。
这里可以找到更多信息。
它们的作用域是什么?
已经在顶部给出了。
“如果您知道在编译之前需要分配多少数据,并且它不太大,那么可以使用堆栈。如果您不知道在运行时需要多少数据,或者需要分配大量数据,则可以使用堆。“
在这里可以找到更多信息。
是什么决定了每个堆的大小?
堆栈的大小是由操作系统在创建线程时设置的。堆的大小是在应用程序启动时设置的,但它可以随着空间的需要而增长(分配器从操作系统请求更多内存)。
什么使堆更快?
堆栈分配要快得多,因为它真正做的就是移动堆栈指针。使用内存池,您可以从堆分配中获得类似的性能,但这会带来轻微的复杂性和它自己的头痛。
此外,堆栈与堆不仅是性能考虑;它还告诉您关于对象的预期寿命。
可以从这里找到详细信息。

2020-02-02 19:35:18
闇の福音

36


上世纪80年代,UNIX像兔子一样传播,大公司纷纷推出自己的产品。
埃克森美孚也有一个品牌,几十个品牌在历史上消失了。
内存的配置方式由许多实现者自行决定。
一个典型的C程序在内存中是平放的,
有机会通过更改brk()值来增加。
通常,堆刚好低于这个brk值
并且增加brk会增加可用堆的数量。
单个堆栈通常是堆下的一个区域,在下一个固定内存块的顶部之前,这个区域是一块不包含任何值的内存
区域。
下一个块通常是可以由堆栈数据覆盖的代码一个典型的内存块是BSS(一个零值块)
它在一个制造商的产品中意外地没有归零。
另一个是包含初始化值的数据,包括字符串和数字。
第三个是包含CRT(C runtime)、main、functions的代码,和库。
在UNIX中虚拟内存的出现改变了许多限制。
现在没有客观的理由说明这些块需要连续,
或大小固定,或按特定的方式排序。
当然,在UNIX成为Multics之前,Multics不受这些限制。
这里有一个示意图,显示了一种内存布局在那个时代。

2020-02-02 19:35:18
第14天⊕魔王

33


虚拟内存中每个进程的堆栈、堆和数据:

2020-02-02 19:35:18
没首级的今川义元

25


几分钱:我认为,用图形化的方式绘制内存会更好,而且更简单:

箭头-显示堆栈和堆的增长位置,进程堆栈大小有限制,在OS中定义,线程堆栈大小通常由线程创建API中的参数限制。堆通常由进程最大虚拟内存大小限制,例如32位2-4兆GB。
这样简单的方法:进程堆是通用的,内部的所有线程都是用内存分配的,通常用MalCube()之类的东西。
堆栈是用于存储普通情况下函数返回指针和变量的快速内存,作为函数调用、局部函数变量中的参数处理。

2020-02-02 19:35:18
名字真难取

23


由于有些答案被吹毛求疵,我将贡献我的mite。
令人惊讶的是,没有人提到,不仅在异国语言(PostScript)或平台(Intel Itanium)中,而且在光纤中都会发现多个(即与运行的操作系统级线程数无关)调用堆栈,绿色线程和协程的一些实现。
光纤、绿色线程和协程在许多方面是相似的,这会导致很多混淆。fibers和green线程的区别在于前者使用协作多任务,而后者可以使用协作或抢占式多任务(甚至两者兼有)。关于fibers和coroutines之间的区别,请参见此处。
无论如何,fibers、green threads和coroutines的目的都是让多个函数在一个操作系统级线程中同时执行,而不是并行执行(有关区别,请参见此问题),以有组织的方式在彼此之间来回传输控制。
当使用光纤、绿色线程或协程时,每个函数通常有一个单独的堆栈。(从技术上讲,不只是一个堆栈,而是每个函数都有一个完整的执行上下文。最重要的是,CPU寄存器。)对于每个线程,堆栈的数量与并发运行的函数的数量一样多,线程根据程序的逻辑在执行每个函数之间切换。当一个函数运行到它的末尾时,它的堆栈就会被破坏。因此,堆栈的数量和生命周期是动态的,而不是由操作系统级线程的数量决定的!
注意,我说过“每个函数通常有一个单独的堆栈”。coutroutines有stackful和stackless两种实现。最值得注意的StcFoost C++实现是Boost。Coroutine和微软PPL的Prime>代码> [0 ] < /代码> 。(但是,C++的可恢复函数(A.K.A.)PRE> >代码> [ 1 ] < /PRE>和
 >代码> [ 2 ] < /代码>  >,可能使用无栈协同程序。
C++标准库的光纤建议即将到来。还有一些第三方图书馆。绿色线程在Python和Ruby等语言中非常流行。

2020-02-02 19:35:18
bbawg

16


我有东西要分享,虽然主要问题已经讨论过了。
堆栈
访问速度非常快。
存储在RAM中。
函数调用与传递的本地变量和函数参数一起加载在这里。
当程序超出范围时,空间会自动释放。
存储在顺序内存中。

堆栈访问速度相对较慢。
存储在在RAM中,
动态创建的变量存储在这里,以后需要在使用后释放分配的内存。
存储在内存分配完成的任何地方,始终由指针访问。
有趣的注意:
如果函数调用存储在堆中,这将导致两个混乱的点:

由于堆栈中的顺序存储,执行速度更快。堆中的存储将导致巨大的时间消耗,从而使整个程序执行速度变慢。
如果函数存储在堆中(指针指向的凌乱存储),则无法返回调用者地址(由于内存中的顺序存储,堆栈会返回到调用者地址)。

2020-02-02 19:35:18
新房

14


哇!这么多的答案,我想其中一个没有答对……
1)它们在哪里,在什么地方(物理上在真正的计算机内存中)?
堆栈是从分配给程序映像的最高内存地址开始的内存,然后从该地址开始值减小。它是为被调用的函数参数和函数中使用的所有临时变量保留的。
有两个堆:public和private。
私有堆从程序中最后一个字节后的16字节边界(对于64位程序)或8字节边界(对于32位程序)开始,然后从那里增加值。它也被称为默认堆。
如果私有堆太大,它将重叠堆栈区域,如果堆栈太大,它也将重叠堆。因为堆栈从一个更高的地址开始,一直向下到更低的地址,通过适当的黑客攻击,可以使堆栈变得如此大,以至于它会溢出私有堆区域并重叠代码区域。接下来的诀窍是重叠足够多的代码区域,以便可以挂接到代码中。这样做有点棘手,可能会导致程序崩溃,但很容易而且非常有效。
公共堆位于程序映像空间之外的自己的内存空间中。如果内存资源变得稀缺的话,就是这个内存会被吸走到硬盘上。
2)它们在多大程度上是由操作系统或语言运行时控制的?
堆栈由程序员控制,私有堆由操作系统管理,公共堆不受任何人控制,因为它是一个操作系统服务——你发出请求,它们要么被授予要么被拒绝。
2b)它们的作用域是什么?
它们对程序来说都是全局的,但是它们的内容可以是私有的、公共的或全局的。
2c)是什么决定了它们的大小?
堆栈和私有堆的大小由编译器运行时选项决定。公共堆在运行时使用大小参数初始化。
2d)什么使其更快?
它们不是被设计成快速的,而是被设计成有用的。程序员如何使用它们决定它们是“快”还是“慢”
参考:
https://norasandler.com/2019/02/18/Write-a-Compiler-10.html
https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf heapapi getprocessheap
https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf heapapi heapic创建

2020-02-02 19:35:18
非灾


许多答案作为概念是正确的,但我们必须注意,硬件(即微处理器)需要一个堆栈来允许调用子例程(在汇编语言中调用)。(面向对象的人会称之为方法)
在堆栈上,您保存返回地址并调用→push/ret→pop直接在硬件中管理。
您可以使用堆栈传递参数。。即使它比使用寄存器慢(微处理器专家会说还是一本上世纪80年代的BIOS手册……)
没有堆栈,微处理器也无法工作。(即使在汇编语言中,我们也无法想象一个程序没有子程序/函数)
没有堆。(汇编语言程序可以不用,因为堆是一个操作系统概念,就像malloc,也就是操作系统/库调用一样。
堆栈的使用速度更快,因为:
是硬件,甚至push/pop都非常有效。
malloc需要进入内核模式,使用lock/semaphore(或其他同步原语)执行一些代码并管理一些需要的结构跟踪分配。

2020-02-02 19:35:18
最澄



Copyright © 2019-2020 zimt8.com
备案号:湘ICP备19012068号