mat*_*ane 7847 language-agnostic heap stack memory-management dynamic-memory-allocation
编程语言书籍解释了在堆栈上创建了值类型,并且在堆上创建了引用类型,而没有解释这两者是什么.我还没有看清楚这个问题.我理解堆栈是什么.但,
Jef*_*ill 5804
堆栈是作为执行线程的临时空间留出的内存.调用函数时,会在堆栈顶部为区域变量和一些簿记数据保留一个块.当该函数返回时,该块将变为未使用状态,并可在下次调用函数时使用.堆栈始终以LIFO(后进先出)顺序保留; 最近保留的块始终是要释放的下一个块.这使得跟踪堆栈非常简单; 从堆栈中释放块只不过是调整一个指针.
堆是为动态分配留出的内存.与堆栈不同,堆中的块的分配和释放没有强制模式; 您可以随时分配一个块并随时释放它.这使得在任何给定时间跟踪堆的哪些部分被分配或释放变得更加复杂; 有许多自定义堆分配器可用于调整不同使用模式的堆性能.
每个线程都获得一个堆栈,而应用程序通常只有一个堆(尽管为不同类型的分配设置多个堆并不罕见).
直接回答您的问题:
它们在多大程度上受操作系统或语言运行时控制?
操作系统在创建线程时为每个系统级线程分配堆栈.通常,语言运行库调用OS来为应用程序分配堆.
它们的范围是什么?
堆栈附加到线程,因此当线程退出堆栈时回收.堆通常在应用程序启动时由运行时分配,并在应用程序(技术过程)退出时回收.
是什么决定了它们的大小?
创建线程时设置堆栈的大小.堆的大小在应用程序启动时设置,但可以在需要空间时增长(分配器从操作系统请求更多内存).
是什么让一个更快?
堆栈更快,因为访问模式使得从中分配和释放内存变得微不足道(指针/整数简单地递增或递减),而堆在分配或释放中涉及更复杂的簿记.此外,堆栈中的每个字节都经常被频繁地重用,这意味着它往往被映射到处理器的缓存,使其非常快.堆的另一个性能损失是堆(主要是全局资源)通常必须是多线程安全的,即每个分配和释放需要 - 通常 - 与程序中的"所有"其他堆访问同步.
明确的示范:
图片来源:vikashazrati.wordpress.com
Bri*_*ndy 2279
堆:
堆:
delete,delete[],或free.new或分配malloc.例:
int foo()
{
char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
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];
}//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;
Run Code Online (Sandbox Code Playgroud)
tho*_*ter 1342
最重要的一点是堆和堆栈是可以分配内存的方式的通用术语.它们可以以多种不同的方式实现,并且这些术语适用于基本概念.
在一堆物品中,物品按照放置在那里的顺序一个在另一个上面,你只能移除顶部的物品(不会将整个物品翻倒).

堆栈的简单性在于您不需要维护包含已分配内存的每个部分的记录的表; 您需要的唯一状态信息是指向堆栈末尾的单个指针.要分配和取消分配,只需递增和递减该单个指针即可.注意:有时可以实现堆栈从一部分内存的顶部开始并向下延伸而不是向上扩展.
在堆中,对项目的放置方式没有特定的顺序.您可以按任意顺序进入和移除项目,因为没有明确的"顶部"项目.

堆分配需要维护已分配内存和不分配内存的完整记录,以及减少碎片的一些开销维护,找到足以满足请求大小的连续内存段,等等.内存可以在任何时候释放,留下自由空间.有时,内存分配器将执行维护任务,例如通过移动已分配的内存来进行内存碎片整理,或者进行垃圾收集 - 在运行时识别内存不再在范围内并解除分配时.
这些图像应该可以很好地描述在堆栈和堆中分配和释放内存的两种方法.百胜!
它们在多大程度上受操作系统或语言运行时控制?
如前所述,堆和堆栈是通用术语,可以通过多种方式实现.计算机程序通常具有称为调用堆栈的堆栈,其存储与当前函数相关的信息,例如指向调用它的任何函数的指针,以及任何局部变量.因为函数调用其他函数然后返回,所以堆栈增大和缩小以保持来自调用堆栈中的函数的信息.程序实际上没有运行时控制权; 它取决于编程语言,操作系统甚至系统架构.
堆是一个通用术语,用于动态和随机分配的任何内存; 即无序.内存通常由OS分配,应用程序调用API函数来执行此分配.管理动态分配的内存需要相当大的开销,这通常由操作系统处理.
它们的范围是什么?
调用堆栈是一种低级概念,它与编程意义上的"范围"无关.如果您反汇编某些代码,您将看到对堆栈部分的相对指针样式引用,但就更高级别的语言而言,该语言强加了自己的范围规则.但是,堆栈的一个重要方面是,一旦函数返回,该函数的任何本地函数都会立即从堆栈中释放出来.考虑到编程语言的工作原理,它的工作方式与预期的方式相同.在堆中,它也很难定义.范围是操作系统公开的内容,但是您的编程语言可能会添加有关应用程序中"范围"的规则.处理器体系结构和OS使用虚拟寻址,处理器将虚拟寻址转换为物理地址,并存在页面错误等.它们跟踪哪些页面属于哪些应用程序.但是,您永远不必担心这一点,因为您只需使用编程语言用于分配和释放内存的任何方法,并检查错误(如果分配/释放因任何原因失败).
是什么决定了它们的大小?
同样,它取决于语言,编译器,操作系统和体系结构.堆栈通常是预先分配的,因为根据定义它必须是连续的内存(更多内容在最后一段中).语言编译器或OS确定其大小.你不会在堆栈上存储大量的数据,所以它应该足够大,永远不应该被完全使用,除非出现不必要的无限递归(因此,"堆栈溢出")或其他不寻常的编程决策.
堆是可以动态分配的任何东西的通用术语.根据您的观察方式,它会不断变化.在现代处理器和操作系统中,它的工作方式无论如何都是非常抽象的,所以你通常不需要担心它如何深入工作,除了(在它允许你的语言中)你不能使用的内存你尚未分配或已释放的记忆.
是什么让一个更快?
堆栈更快,因为所有可用内存始终是连续的.不需要维护所有可用内存段的列表,只需要指向当前堆栈顶部的单个指针.为此,编译器通常将此指针存储在特殊的快速寄存器中.更重要的是,堆栈上的后续操作通常集中在非常靠近的内存区域,这非常低的水平有利于处理器片上高速缓存的优化.
Mar*_*age 710
(我已经从另一个或多或少是这个问题的问题中提出了这个答案.)
您的问题的答案是特定于实现的,并且可能因编译器和处理器体系结构而异.但是,这是一个简化的解释.
new或malloc).这需要更新堆上的块列表.该元信息在堆上块也存储在堆上经常在小范围只是在每个块的前面.

可以在堆而不是堆栈上分配函数吗?
不,函数的激活记录(即本地或自动变量)在堆栈上分配,不仅用于存储这些变量,还用于跟踪嵌套函数调用.
如何管理堆实际上取决于运行时环境.C使用malloc和C++使用new,但许多其他语言都有垃圾收集.
但是,堆栈是与处理器架构紧密相关的更低级别的功能.当没有足够的空间时增加堆不是太难,因为它可以在处理堆的库调用中实现.但是,堆栈的增长通常是不可能的,因为只有在为时已晚时才发现堆栈溢出; 并且关闭执行线程是唯一可行的选择.
Sno*_*ash 395
在以下C#代码中
public void Method1()
{
int i = 4;
int y = 2;
class1 cls1 = new class1();
}
Run Code Online (Sandbox Code Playgroud)
以下是内存的管理方式

Local Variables只要函数调用进入堆栈,只需要持续.堆用于变量,我们事先并不知道它们的生命周期,但我们希望它们可以持续一段时间.在大多数语言中,如果我们想要将它存储在堆栈中,那么在编译时我们知道变量的大小是至关重要的.
对象(在更新它们时大小不同)会在堆上进行,因为我们在创建时不知道它们将持续多长时间.在许多语言中,堆被垃圾收集以查找不再具有任何引用的对象(例如cls1对象).
在Java中,大多数对象直接进入堆.在像C/C++这样的语言中,当你不处理指针时,结构和类通常可以保留在堆栈中.
更多信息可以在这里找到:
和这里:
本文是上图的来源:六个重要的.NET概念:堆栈,堆,值类型,引用类型,装箱和拆箱 - CodeProject
但请注意,它可能包含一些不准确之处.
Tom*_*eys 202
堆栈 当你调用一个函数时,该函数的参数加上一些其他开销被放在堆栈上.一些信息(例如返回的地方)也存储在那里.在函数内部声明变量时,该变量也会在堆栈中分配.
取消分配堆栈非常简单,因为您总是按照分配的相反顺序解除分配.输入函数时会添加堆栈内容,退出时会删除相应的数据.这意味着您倾向于保持在堆栈的一个小区域内,除非您调用许多调用许多其他函数的函数(或创建递归解决方案).
该堆 的堆是,你把你的飞行创建数据的通用名称.如果您不知道程序将创建多少太空飞船,您可能会使用新的(或malloc或等效的)运算符来创建每个太空飞船.这种分配将持续一段时间,因此我们很可能会以与创建它们不同的顺序释放事物.
因此,堆要复杂得多,因为最终存在未使用的内存区域与内存被分段的块交织.找到所需大小的空闲内存是一个难题.这就是应该避免堆的原因(虽然它仍然经常使用).
实现 堆栈和堆的实现通常都是运行时/操作系统.通常,性能至关重要的游戏和其他应用程序会创建自己的内存解决方案,从堆中获取大量内存,然后在内部将其清除,以避免依赖操作系统获取内存.
这只有在你的内存使用量与标准有很大不同的情况下才有用 - 即对于你在一个巨大的操作中加载一个级别并且可以在另一个巨大的操作中丢掉所有内存的游戏.
内存中的物理位置 由于称为虚拟内存的技术使您的程序认为您可以访问物理数据位于其他位置的某个地址(即使在硬盘上!),因此与您的想法相关性较低.当您的调用树变得更深时,您获得的堆栈地址会逐渐增加.堆的地址是不可预测的(即特定于implimentation),坦率地说并不重要.
dav*_*vec 188
澄清一下,这个答案有不正确的信息(托马斯在评论后修正了他的答案,很酷:)).其他答案只是避免解释静态分配的含义.因此,我将解释三种主要的分配形式以及它们通常如何与下面的堆,堆栈和数据段相关联.我还将展示C/C++和Python中的一些示例,以帮助人们理解.
"静态"(AKA静态分配)变量未在堆栈上分配.不要这么认为 - 许多人只是因为"静态"听起来很像"堆叠".它们实际上既不存在于堆栈中,也不存在于堆中.这是所谓的数据段的一部分.
但是,通常最好考虑" 范围 "和" 生命周期 "而不是"堆栈"和"堆".
范围是指代码的哪些部分可以访问变量.通常我们会考虑局部范围(只能通过当前函数访问)与全局范围(可以在任何地方访问),尽管范围可能变得更加复杂.
生命周期是指在程序执行期间分配和解除分配变量的时间.通常我们会想到静态分配(变量将持续整个程序的持续时间,使其对于在多个函数调用中存储相同的信息很有用)与自动分配(变量仅在单个函数调用期间持续存在,使其对于存储仅在函数期间使用的信息,并且一旦完成就可以丢弃)与动态分配(变量的持续时间在运行时定义,而不是像静态或自动一样的编译时).
尽管大多数编译器和解释器在使用堆栈,堆等方面类似地实现了这种行为,但只要行为正确,编译器有时可能会破坏这些约定.例如,由于优化,局部变量可能只存在于寄存器中或被完全删除,即使堆栈中存在大多数局部变量.正如在一些注释中指出的那样,你可以自由地实现一个甚至不使用堆栈或堆的编译器,而是使用其他一些存储机制(很少做,因为堆栈和堆很适合这个).
我将提供一些简单的带注释的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.
Run Code Online (Sandbox Code Playgroud)
为什么区分生命周期和范围很重要的一个特别尖锐的例子是变量可以具有局部范围但是具有静态生命周期 - 例如,上面的代码示例中的"someLocalStaticVariable".这些变量可以使我们共同但非正式的命名习惯非常混乱.例如,当我们说" 本地 "时,我们通常意味着" 本地范围自动分配变量 ",当我们说全局时,我们通常表示" 全局范围的静态分配变量 ".不幸的是,当涉及到" 文件范围静态分配变量 " 这样的事情时,很多人只会说......" 嗯??? ".
C/C++中的一些语法选择加剧了这个问题 - 例如,由于下面显示的语法,许多人认为全局变量不是"静态的".
int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation
int main() {return 0;}
Run Code Online (Sandbox Code Playgroud)
请注意,在上面的声明中放置关键字"static"可以防止var2具有全局范围.然而,全局var1具有静态分配.这不直观!出于这个原因,我尝试在描述范围时从不使用"静态"一词,而是说"文件"或"文件限制"范围.然而,许多人使用短语"静态"或"静态范围"来描述只能从一个代码文件访问的变量.在生命周期的上下文中,"static" 总是表示变量在程序启动时分配,并在程序退出时释放.
有些人认为这些概念是特定于C/C++的.他们不是.例如,下面的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
Run Code Online (Sandbox Code Playgroud)
Don*_*eld 165
其他人已经很好地回答了广泛的笔触,所以我会提出一些细节.
堆栈和堆不必是单数.如果您在一个进程中有多个线程,那么您有多个堆栈的常见情况.在这种情况下,每个线程都有自己的堆栈.您也可以拥有多个堆,例如某些DLL配置可能会导致从不同的堆分配不同的DLL,这就是为什么释放由不同库分配的内存通常是个坏主意.
在C语言中,你可以通过使用获得的可变长度分配的利益ALLOCA,其分配在栈上,而不是Alloc,可分配在堆上.这个内存不会在你的return语句中存活,但它对临时缓冲区很有用.
在Windows上创建一个巨大的临时缓冲区并不是免费的.这是因为编译器将生成每次输入函数时调用的堆栈探测循环,以确保堆栈存在(因为Windows使用堆栈末尾的单个防护页面来检测何时需要增加堆栈.如果你从堆栈的末尾访问多个页面的内存,你将崩溃).例:
void myfunction()
{
char big[10000000];
// Do something that only uses for first 1K of big 99% of the time.
}
Run Code Online (Sandbox Code Playgroud)
bk1*_*k1e 133
其他人直接回答了你的问题,但在尝试理解堆栈和堆时,我认为考虑传统UNIX进程的内存布局(没有线程和mmap()基于分配器)是有帮助的.该内存管理词汇网页都有此内存布局的示意图.
堆栈和堆传统上位于进程的虚拟地址空间的两端.堆栈在访问时会自动增长,最大可达内核设置的大小(可以调整setrlimit(RLIMIT_STACK, ...)).当内存分配器调用brk()或sbrk()系统调用时,堆会增长,将更多页面的物理内存映射到进程的虚拟地址空间.
在没有虚拟内存的系统中,例如某些嵌入式系统,通常会应用相同的基本布局,但堆栈和堆的大小是固定的.但是,在其他嵌入式系统(例如基于Microchip PIC单片机的系统)中,程序堆栈是一个单独的内存块,无法通过数据移动指令寻址,只能通过程序流指令间接修改或读取(调用,返回等).其他架构(如Intel Itanium处理器)具有多个堆栈.从这个意义上说,堆栈是CPU架构的一个元素.
Dan*_*ian 113
堆栈是内存的一部分,可以通过几个关键的汇编语言指令来操作,例如'pop'(从堆栈中删除并返回一个值)和'push'(将值推送到堆栈),还可以调用(调用子程序 - 这会推送地址返回堆栈)并返回(从子程序返回 - 这会弹出堆栈中的地址并跳转到它).它是堆栈指针寄存器下面的内存区域,可以根据需要进行设置.堆栈还用于将参数传递给子例程,也用于在调用子例程之前保留寄存器中的值.
堆是操作系统给应用程序的内存的一部分,通常通过类似malloc的系统调用.在现代操作系统上,此内存是一组只有调用进程才能访问的页面.
堆栈的大小在运行时确定,并且通常在程序启动后不会增长.在C程序中,堆栈需要足够大以容纳每个函数中声明的每个变量.堆将根据需要动态增长,但操作系统最终会进行调用(它通常会使堆的增长超过malloc请求的值,因此至少某些未来的malloc将不需要返回到内核获得更多内存.此行为通常是可自定义的)
因为你在启动程序之前已经分配了堆栈,所以在使用堆栈之前你永远不需要malloc,所以这是一个小优势.在实践中,很难预测具有虚拟内存子系统的现代操作系统的速度和速度会有多快,因为页面的实现方式和存储位置是实现细节.
小智 112
我想很多其他人在这件事上给了你大致正确的答案.
然而,遗漏的一个细节是"堆"实际上可能被称为"免费商店".这种区别的原因是原始的免费存储是使用称为"二项式堆"的数据结构实现的.出于这个原因,从malloc()/ free()的早期实现中分配是从堆中分配的.然而,在这个现代,大多数免费商店都使用非二维堆的非常精细的数据结构来实现.
Shr*_*ari 111
什么是堆栈?
堆叠是一堆物体,通常是整齐排列的物体.
计算体系结构中的堆栈是存储器区域,其中以后进先出的方式添加或移除数据.
在多线程应用程序中,每个线程都有自己的堆栈.
什么是堆?
堆是随意堆积的不整齐的东西.
在计算体系结构中,堆是动态分配的内存区域,由操作系统或内存管理器库自动管理.
在程序执行期间,堆上的内存会被定期分配,释放和调整大小,这可能会导致称为碎片的问题.
当内存对象被分配时,它们之间的小空间太小而无法容纳额外的内存对象,就会发生碎片.
最终结果是堆空间的百分比不可用于进一步的内存分配.
两者一起
在多线程应用程序中,每个线程都有自己的堆栈.但是,所有不同的线程都将共享堆.
因为不同的线程在多线程应用程序中共享堆,这也意味着线程之间必须有一些协调,这样它们就不会尝试访问和操作堆中的同一块内存.同一时间.
哪个更快 - 堆栈还是堆?为什么?
堆栈比堆快得多.
这是因为在堆栈上分配内存的方式.
在堆栈上分配内存就像向上移动堆栈指针一样简单.
对于刚接触编程的人来说,使用堆栈可能是一个好主意,因为它更容易.
由于堆栈很小,因此当您确切知道数据需要多少内存时,或者如果您知道数据的大小非常小时,则需要使用它.
当您知道数据需要大量内存时,最好使用堆,或者您只是不确定需要多少内存(例如动态数组).
堆栈是存储局部变量(包括方法参数)的存储区域.对于对象变量,这些仅仅是对堆上实际对象的引用(指针).
每次实例化一个对象时,都会留出一大堆堆内存来保存该对象的数据(状态).由于对象可以包含其他对象,因此某些数据实际上可以保存对这些嵌套对象的引用.
T.E*_*.D. 87
简单地说,堆栈是创建局部变量的地方.此外,每次调用子程序时,程序计数器(指向下一个机器指令的指针)和任何重要的寄存器,有时参数都会被压入堆栈.然后子程序中的任何局部变量被压入堆栈(并从那里使用).当子程序结束时,所有东西都会从堆栈中弹出.PC和寄存器数据在弹出时会被放回原处,因此您的程序可以顺利进行.
堆是内存区域动态内存分配由(显式"新"或"分配"调用)组成.它是一种特殊的数据结构,可以跟踪不同大小的内存块及其分配状态.
在"经典"系统中,RAM被布置成使得堆栈指针从内存的底部开始,堆指针从顶部开始,并且它们朝向彼此增长.如果它们重叠,则表示RAM不足.但这不适用于现代多线程操作系统.每个线程都必须有自己的堆栈,这些堆栈可以动态创建.
dev*_*Xen 81
来自WikiAnwser.
当函数或方法调用另一个函数,该函数又调用另一个函数等时,所有这些函数的执行将保持挂起,直到最后一个函数返回其值.
这个挂起函数调用链是堆栈,因为堆栈中的元素(函数调用)彼此依赖.
在异常处理和线程执行中,堆栈很重要.
堆只是程序用来存储变量的内存.堆的元素(变量)彼此之间没有依赖关系,并且可以随时随机访问.
unk*_*own 52
堆
堆
Abr*_*hin 44
堆栈用于静态内存分配,堆用于动态内存分配,两者都存储在计算机的RAM中.
堆栈
堆栈是一个"LIFO"(后进先出)数据结构,由CPU非常密切地管理和优化.每次函数声明一个新变量时,它都会被"推"到堆栈上.然后每次函数退出时,该函数推送到堆栈的所有变量都被释放(也就是说,它们被删除).一旦释放了堆栈变量,该内存区域就可用于其他堆栈变量.
使用堆栈存储变量的优点是可以为您管理内存.您不必手动分配内存,也不必在不再需要时释放内存.更重要的是,因为CPU如此高效地组织堆栈内存,读取和写入堆栈变量的速度非常快.
更多信息可以在这里找到.
堆
堆是计算机内存的一个区域,不会自动为您管理,并且不受CPU的严格管理.它是一个更自由浮动的内存区域(并且更大).要在堆上分配内存,必须使用malloc()或calloc(),它们是内置的C函数.一旦你在堆上分配了内存,你就有责任使用free()在你不再需要它时解除分配该内存.
如果您没有这样做,您的程序将具有所谓的内存泄漏.也就是说,堆上的内存仍然会被搁置(并且不会被其他进程使用).正如我们将在调试部分中看到的,有一个名为Valgrind的工具可以帮助您检测内存泄漏.
与堆栈不同,堆对可变大小没有大小限制(除了计算机明显的物理限制).堆内存的读取和写入速度稍慢,因为必须使用指针来访问堆上的内存.我们将很快讨论指针.
与堆栈不同,堆上创建的变量可由程序中的任何位置的任何函数访问.堆变量本质上是全局的.
更多信息可以在这里找到.
在堆栈上分配的变量直接存储到存储器中,并且对该存储器的访问非常快,并且在编译程序时处理其分配.当函数或方法调用另一个函数,该函数又调用另一个函数等时,所有这些函数的执行将保持挂起,直到最后一个函数返回其值.堆栈始终以LIFO顺序保留,最近保留的块始终是要释放的下一个块.这使得跟踪堆栈非常简单,从堆栈中释放块只不过是调整一个指针.
在堆上分配的变量在运行时分配了内存并且访问此内存有点慢,但堆大小仅受虚拟内存大小的限制.堆的元素彼此之间没有依赖关系,并且可以随时随机访问.您可以随时分配一个块并随时释放它.这使得在任何给定时间跟踪堆的哪些部分被分配或释放变得更加复杂.
如果您确切地知道在编译之前需要分配多少数据,那么您可以使用堆栈,并且它不会太大.如果您不确切知道运行时需要多少数据,或者需要分配大量数据,则可以使用堆.
在多线程情况下,每个线程都有自己完全独立的堆栈,但它们将共享堆.堆栈是特定于线程的,堆是特定于应用程序的.在异常处理和线程执行中,堆栈很重要.
每个线程都获得一个堆栈,而应用程序通常只有一个堆(尽管为不同类型的分配设置多个堆并不罕见).
在运行时,如果应用程序需要更多堆,它可以从空闲内存分配内存,如果堆栈需要内存,它可以从应用程序的空闲内存分配内存中分配内存.
现在来看你的问题的答案.
它们在多大程度上受操作系统或语言运行时控制?
操作系统在创建线程时为每个系统级线程分配堆栈.通常,语言运行库调用OS来为应用程序分配堆.
更多信息可以在这里找到.
它们的范围是什么?
已经在顶部给出.
"如果你确切地知道在编译之前需要分配多少数据,你就可以使用堆栈.它不是太大.如果你不确切知道在运行时需要多少数据,你可以使用堆.你需要分配很多数据."
更多信息可以在这里找到.
是什么决定了它们的大小?
创建线程时,OS的大小由OS设置.堆的大小在应用程序启动时设置,但它可以在需要空间时增长(分配器从操作系统请求更多内存).
是什么让一个更快?
堆栈分配要快得多,因为它真正做的就是移动堆栈指针.使用内存池,您可以从堆分配中获得可比较的性能,但这会带来轻微的复杂性和自身的麻烦.
此外,堆栈与堆不仅是性能考虑因素; 它还告诉你很多关于对象的预期寿命.
细节可以在这里找到.
Ali*_*eza 44
好吧,简单而言之,它们意味着有序而不是有序 ......!
堆栈:在堆栈项目中,事物处于彼此的顶部,意味着要更快,更高效地处理!...
所以总有一个索引指向特定项目,处理速度也会更快,项目之间也存在关联!...
堆:没有订单,处理速度会慢,价值会混乱,没有特定的订单或索引......有随机的,它们之间没有关系......所以执行和使用时间可能会有所不同......
我还创建了下面的图像,以显示它们的外观:
小智 35
在20世纪80年代,UNIX像兔子一样传播,大公司自己推销.埃克森只有一个和历史上失去的几十个品牌一样.如何记忆是由许多实施者自行决定的.
典型的C程序在内存中平放,有机会通过更改brk()值来增加.通常,HEAP刚好低于此brk值,并且增加的brk增加了可用堆的数量.
单个STACK通常是HEAP下面的一个区域,它是一个内存区域,直到下一个固定内存块的顶部才包含任何有价值的内容.下一个块通常是CODE,它可以在其时代的一个着名黑客中被堆栈数据覆盖.
一个典型的存储器块是BSS(一个零值块),它在一个制造商的产品中偶然没有归零.另一个是包含初始化值的DATA,包括字符串和数字.第三个是包含CRT(C运行时),main,函数和库的CODE.
UNIX中虚拟内存的出现改变了许多约束.没有客观理由说明为什么这些块需要连续,或固定大小,或现在以特定方式排序.当然,在UNIX之前没有遭受这些限制的Multics.这是一个显示那个时代的存储器布局之一的示意图.

Max*_*niy 24
几美分:我认为,绘制内存图形化更简单:
箭头 - 显示增长堆栈和堆的位置,进程堆栈大小有限制,在OS中定义,线程堆栈大小通常由线程创建API中的参数限制.堆通常限制为进程最大虚拟内存大小,例如32位2-4 GB.
这么简单的方法:进程堆通常用于进程和内部的所有线程,在常见情况下用于内存分配,例如malloc().
堆栈是快速存储器,用于存储常见情况函数返回指针和变量,作为函数调用中的参数处理,本地函数变量.
sha*_*rov 22
由于一些答案被挑剔,我将贡献我的螨虫.
令人惊讶的是,没有人提到多个(即与运行的OS级线程的数量无关)调用堆栈不仅可以在外来语言(PostScript)或平台(Intel Itanium)中找到,还可以在光纤,绿色线程中找到协同程序的一些实现.
纤维,绿线和协同程序在很多方面都很相似,这会导致很多混乱.光纤和绿色线程之间的区别在于前者使用协作式多任务处理,而后者可能具有协作式或抢占式(或甚至两者).有关纤维和协程之间的区别,请参见此处.
在任何情况下,光纤,绿色线程和协同程序的目的是在一个OS级别的线程中同时执行多个函数,但不能并行执行(参见这个区别的SO问题),相互之间来回传递控制以有组织的方式.
使用光纤,绿色线程或协程时,每个函数通常都有一个单独的堆栈.(从技术上讲,不仅仅是一个堆栈,而是整个执行上下文是每个函数.最重要的是,CPU寄存器.)对于每个线程,存在与并发运行函数一样多的堆栈,并且线程在执行每个函数之间切换根据你的程序的逻辑.当函数运行到其末尾时,其堆栈将被销毁.因此,堆栈的数量和生命周期是动态的,并不是由OS级线程的数量决定的!
请注意,我说" 每个函数通常有一个单独的堆栈".有俩都stackful和无堆叠 couroutines的实现.最值得注意的stackful C++实现是Boost.Coroutine和微软PPL的async/await.(但是,C++的可恢复函数(又名" async和await"),它们被提议用于C++ 17,可能会使用无堆栈协程.)
纤维对C++标准库的提议即将发布.此外,还有一些第三方库.绿色线程在Python和Ruby等语言中非常流行.
Pan*_*apa 16
我有一些东西要分享,虽然已经涵盖了主要观点.
堆
堆
有趣的说明:
小智 14
哇!如此众多的答案,我认为其中没有一个答案正确...
1)它们在哪里和什么(物理上在真实计算机的内存中)?
堆栈是从分配给程序映像的最高内存地址开始的内存,然后从那里递减值。它保留给调用的函数参数以及函数中使用的所有临时变量。
有两个堆:公共堆和私有堆。
私有堆从程序中代码的最后一个字节之后的16字节边界(对于64位程序)或8字节边界(对于32位程序)开始,然后从那里开始增加值。也称为默认堆。
如果私有堆太大,则它将与堆栈区域重叠,如果私有堆太大,则堆栈将与堆栈重叠。由于堆栈从较高的地址开始,然后一直向下移动至较低的地址,因此通过适当的破解,您可以使堆栈变大,以至于溢出专用堆区域并重叠代码区域。然后,诀窍是要重叠足够的代码区域,以便可以将其连接到代码中。这样做有些棘手,您可能会遇到程序崩溃的风险,但它既简单又有效。
公共堆位于程序映像空间之外的自己的内存空间中。如果内存资源稀缺,则会将该内存虹吸到硬盘上。
2)它们在多大程度上受操作系统或语言运行时的控制?
堆栈由程序员控制,私有堆由OS管理,而公共堆则不受任何人控制,因为它是OS服务-您发出请求,然后授予或拒绝请求。
2b)他们的范围是什么?
它们对于该程序都是全局的,但是其内容可以是私有的,公共的或全局的。
2c)是什么决定了每个尺寸?
堆栈和专用堆的大小由编译器运行时选项确定。公共堆在运行时使用size参数初始化。
2d)是什么使速度更快?
它们并不是为了快速而设计的,而是被设计为有用的。程序员如何利用它们确定它们是“快速”还是“慢速”
参考:
https://norasandler.com/2019/02/18/Write-a-Compiler-10.html
https://docs.microsoft.com/zh-CN/windows/desktop/api/heapapi/nf-heapapi-getprocessheap
https://docs.microsoft.com/zh-CN/windows/desktop/api/heapapi/nf-heapapi-heapcreate
A. *_*dry 13
它们在哪里、是什么(在真实计算机的内存中)?
答案: 两者都在 RAM 中。
在旁边:
RAM 就像一张桌子,HDD/SSD(永久存储)就像书架。要阅读任何内容,您必须在桌子上打开一本书,并且您只能在桌子上打开尽可能多的书。要获取一本书,您可以从书架上取出它并在桌子上打开它。要归还一本书,您可以将桌子上的书合上,然后将其放回书架上。
堆栈和堆是编译器在同一位置(即 RAM)存储不同类型数据的两种方式的名称。
他们的范围是什么?
是什么决定了它们各自的大小?
是什么让一个人更快?
回答:
堆栈用于静态(固定大小)数据
A。在编译时,编译器读取代码中使用的变量类型。
我。它为这些变量分配固定数量的内存。
二. 该内存的大小无法增长。
b. 内存是连续的(单个块),因此访问 有时 比堆更快
C。放置在堆栈上的对象在运行时在内存中增长超出堆栈大小会导致 堆栈溢出错误
堆用于动态(改变大小)数据
A。内存量仅受 RAM
i 中可用空闲空间量的限制。使用的数量可以在运行时根据需要增加或减少
b. 由于项目是通过查找 RAM 中存在的空白空间来在堆上分配的,因此数据并不总是位于连续的部分,这 有时 会导致访问速度比堆栈慢
C。程序员使用关键字手动将项目放入堆上new,并且在使用完该内存后必须手动释放该内存。
我。重复分配新内存而不在不再需要时释放它的代码会导致内存泄漏。
在旁边:
引入栈和堆的主要目的并不是为了提高速度;而是为了提高速度。引入它们是为了处理内存溢出。关于使用堆栈与堆的第一个问题应该是是否会发生内存溢出。如果一个对象的大小打算增长到未知量(例如链表或其成员可以保存任意数量数据的对象),请将其放置在堆上。尽可能使用 C++ 标准库 (STL) 容器向量、映射和列表,因为它们具有内存和速度效率,并且添加后可以让您的生活更轻松(您无需担心内存分配/释放)。
在让你的代码运行之后,如果你发现它运行速度慢得令人无法接受,那么返回并重构你的代码,看看是否可以更有效地编程。结果可能是问题与堆栈或堆根本没有直接关系(例如,使用迭代算法而不是递归算法,查看 I/O 与 CPU 密集型任务,也许添加多线程或多处理)。
我在上面说有时更慢/更快是因为程序的速度可能与在堆栈或堆上分配的项目没有任何关系。
它们在多大程度上受操作系统或语言运行时的控制?
回答:
堆栈大小由编译器在编译时确定。
堆大小在运行时会发生变化。(堆在运行时与操作系统一起分配内存。)
在旁边:
下面是有关控制和编译时与运行时操作的更多信息。
每台计算机都有一个独特的指令集架构(ISA),这是它的硬件命令(例如“MOVE”、“JUMP”、“ADD”等)。
操作系统只不过是一个资源管理器(控制如何/何时/以及在何处使用内存、处理器、设备和信息)。
操作系统的ISA称为裸机,其余命令称为扩展机。内核是扩展机器的第一层。它控制诸如
当我们说“编译器”时,我们通常指的是编译器、汇编器和链接器
机器代码在执行时会传递到内核,内核决定何时应该运行并取得控制权,但机器代码本身包含用于请求文件、请求内存等的 ISA 命令。因此,代码发出 ISA 命令,但一切都必须通过由内核。
许多答案都是正确的概念,但我们必须注意硬件(即微处理器)需要一个堆栈来允许调用子程序(汇编语言中的CALL ......).(OOP人会称之为方法)
在堆栈上保存返回地址并调用→push/ret→pop直接在硬件中管理.
您可以使用堆栈传递参数..即使它比使用寄存器慢(微处理器大师会说或者是一本很好的20世纪80年代的BIOS书......)
堆栈使用速度更快:
我觉得大多数答案都非常复杂和技术性,而我没有找到一个可以简单解释这两个概念背后的推理的答案(即为什么人们首先创建它们?)以及为什么你应该关心。这是我的尝试:
\n就是这样。
\n\xc2\xa0
\n\xc2\xa0
\n不过,为了获得更多解释:
\n堆栈旨在用作临时内存或工作内存,我们知道无论我们在程序的生命周期内放入什么混乱的内存空间,该内存空间都会定期被完全删除。这就像你桌上的备忘录,你在上面写下任何你脑海中闪过的、你几乎感觉不重要的东西,你知道你会在一天结束时扔掉它,因为你已经过滤和组织了真正重要的笔记在另一种媒介中,例如文档或书籍。我们不关心演示、划掉或难以理解的文本,这只是为了我们当天的工作,并且会记住我们一两个小时前的意思,这只是我们存储我们想要记住的想法的快速而肮脏的方式稍后再做,不会伤害我们当前的思绪。这就是人们所说的“堆栈就是暂存器”的意思”的意思。
\n然而,堆是长期存储器,是我们在创建后很长一段时间内存储、查阅和依赖的实际重要文件。因此它需要有完善的形式并严格包含重要数据。这就是为什么它的制作成本很高并且不能用于我们之前的备忘录的用例。在学术论文演示中记下所有笔记,将文本写成书法是不值得的,甚至根本没有用。然而,此演示文稿对于精心策划的数据非常有用。这就是堆的含义。众所周知的数据,对于生命周期应用程序很重要,可以很好地控制并且在代码中的许多地方都需要这些数据。因此,如果您没有明确要求,系统永远不会删除这些宝贵的数据,因为它知道“这就是重要数据的所在!”。
\n这就是为什么您需要管理和处理堆上的内存分配,但不需要为堆栈操心。
\n大多数最佳答案只是该概念在真实计算机中实际实现的技术细节。
\n因此,我们要从中得到的结论是:
\n不重要的、工作的、临时的、只是让我们的函数和对象工作所需的数据(通常)更适合存储在堆栈上。
\n重要的、永久的和基础的应用程序数据(通常)更适合存储在堆上。
\n当然,这只需要在程序的生命周期的背景下考虑。显然,程序生成的实际对人类重要的数据需要存储在外部文件中。(因为无论是堆还是堆栈,当程序终止时它们都会被完全清除。)
\nPS:这些只是一般规则,您总是可以找到边缘情况,每种语言都有自己的实现和由此产生的怪癖,这旨在作为概念的指导和经验法则。
\n| 归档时间: |
|
| 查看次数: |
1453841 次 |
| 最近记录: |