- 栈和堆是什么?
- 它们在计算机内存中的物理位置在哪里?
- 它们在多大程度上受操作系统或语言运行时的控制?
- 它们的作用范围是什么?
- 是什么决定它们的大小?
- 是什么使它们更快?
堆栈:
堆:
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;
C99
语言标准(网址为http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf)定义的 C
语言需要一个“堆栈”。事实上,在标准中甚至没有出现过“堆栈”这个词。总体上而言,关于 C
的堆栈使用情况的说法是正确的,但这并不是该语言必须要求的。请参阅http://www.knosof.co.uk/cbook/cbook.html了解更多信息,特别是有关如何在奇怪的体系结构上实现 C
,例如http://en.wikipedia.org/wiki/Burroughs_large_systems。 - johne这些图片应该可以很好地描述在栈和堆中分配和释放内存的两种方式。美味!
它们在多大程度上受操作系统或语言运行时的控制?
如前所述,堆和栈是通用术语,可以以许多方式实现。计算机程序通常有一个称为调用栈的栈,其中存储与当前函数相关的信息,例如指向调用它的任何函数的指针和任何本地变量。由于函数调用其他函数然后返回,因此堆栈会增长和缩小以容纳来自调用堆栈下方的函数的信息。程序并没有真正的运行时控制权;它由编程语言、操作系统甚至系统架构决定。
堆是一个通用术语,用于分配动态和随机的任何内存;即无序的。通常由操作系统分配内存,应用程序调用API函数来执行此分配。管理动态分配的内存需要相当多的开销,通常由所使用的编程语言或环境的运行时代码处理。
它们的作用域是什么?
调用栈是一个如此低级别的概念,它不涉及到编程中“作用域”的意义。如果你反汇编一些代码,你会看到相对指针样式引用堆栈的部分,但就高级语言而言,语言会强制其自己的作用域规则。然而,堆栈的一个重要方面是,一旦函数返回,任何局部变量都会立即从堆栈中释放。这符合你在编程语言工作时所期望的方式。在堆中,它也很难定义。作用域是由操作系统公开的内容,但你的编程语言可能会为应用程序添加其规则。处理器架构和操作系统使用虚拟寻址,处理器将其转换为物理地址,并存在页面错误等问题。他们跟踪哪些页面属于哪个应用程序。不过,你通常不需要担心这个,因为你只需使用编程语言用于分配和释放内存的任何方法,并检查错误(如果分配/释放由于任何原因失败)。
什么决定了它们的大小?
同样,这取决于语言、编译器、操作系统和体系结构。栈通常是预先分配的,因为根据定义,它必须是连续的内存。语言编译器或操作系统确定其大小。你不会在堆栈上存储大块数据,因此它的大小足够大,应该永远不会被完全使用,除非出现不想要的无限递归(因此,“堆栈溢出”)或其他不寻常的编程决策。
堆是一个通用术语,用于动态分配任何东西。从某种意义上说,它的大小在不断变化。在现代处理器和操作系统中,它的确切工作方式非常抽象,因此通常不需要过多地担心其深层次的工作原理,除非你使用的编程语言允许你使用未分配的内存或已释放的内存。
什么使其中一个更快?
栈更快,因为所有空闲内存总是连续的。无需维护所有空闲内存段的列表,只需维护当前栈顶
new
或malloc
)。这需要更新堆上块的列表。关于堆上块的这些元信息通常也存储在堆上,通常存储在每个块前面的小区域中。一个函数能否被分配到堆上而不是堆栈上?
不行,函数的激活记录(即本地或自动变量)是分配在堆栈上的,堆栈不仅用于存储这些变量,还用于跟踪嵌套函数调用。
堆是如何管理的取决于运行时环境。C 语言使用 malloc
,C++ 使用 new
,但许多其他语言都有垃圾回收。
但是,堆栈是与处理器体系结构密切相关的更低级别的特性。当空间不足时,增加堆并不太困难,因为可以在处理堆的库调用中实现。然而,增加堆栈往往是不可能的,因为只有在过度使用时才会发现堆栈溢出;关闭执行线程是唯一可行的选择。
以下是C#代码
public void Method1()
{
int i = 4;
int y = 2;
class1 cls1 = new class1();
}
以下是内存如何管理的方式:
本地变量
只需在函数调用期间保留,将被放入堆栈中。堆用于生存期我们事先不太知道但我们希望它们持续一段时间的变量。在大多数语言中,如果要将变量存储在堆栈上,则需要在编译时知道变量的大小。
对象
(因为我们无法在创建时知道它们存活的时间)会被放入堆中。在许多语言中,堆会进行垃圾回收以查找不再具有任何引用的对象(例如cls1对象)。
在Java中,大多数对象直接放入堆中。在C/C++等语言中,结构体和类通常在处理指针时仍可以保留在堆栈上。
更多信息可在以下链接中找到:
和这里:
此文章是上面图片的来源:六个重要的.NET概念:堆栈、堆、值类型、引用类型、装箱和拆箱 - CodeProject
但请注意,其中可能存在一些不准确之处。
y
之后,我如何访问i
?我必须弹出y
吗?交换它们?如果有很多本地变量将它们分开怎么办? - confused00其他答案都避免了解释静态分配的含义。因此,我将解释三种主要的分配形式以及它们通常与堆、栈和数据段的关系。我还将在C/C++和Python中提供一些示例,以帮助人们理解。
"Static"(也称为静态分配)变量不是在栈上分配的。不要这样假设——很多人之所以这样做,只是因为 "static"听起来很像 "stack"。它们实际上既不在堆栈中,也不在堆中。它们是所谓的数据段的一部分。
然而,通常最好考虑 "作用域"和 "生存期" 而不是 "栈"和 "堆"。
作用域指的是代码中哪些部分可以访问一个变量。通常,我们认为有 局部作用域(只能被当前函数访问)和 全局作用域(可以在任何地方访问),尽管作用域可能会变得更加复杂。
生存期指的是程序执行期间变量的分配和释放时间。通常,我们认为有 静态分配(变量将在整个程序的持续时间内存在,这对于在多个函数调用之间存储相同信息很有用)和 自动分配(变量仅在单个函数调用期间存在,这对于存储仅在函数期间使用且可以一旦完成就丢弃的信息很有用)以及 动态分配(变量的持续时间在运行时定义,而不是像静态或自动分配那样在编译时定义)。
尽管大多数编译器和解释器在使用堆栈等方面实现此行为的方式类似,但编译器可能会有时打破这些约定,只要行为正确。例如,由于优化,局部变量可能仅存在于寄存器中,或者完全被删除,即使大多数局部变量都存在于堆栈中。正如在一些注释中指出的那样,您可以自由地实现一个甚至不使用堆栈或堆的编译器,而是使用其他存储机制(很少这样做,因为堆栈和堆非常适合这种情况)。
我将提供一些简单的注释的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.
变量的生命周期和作用域需要区分,有些变量虽然具有局部作用域但是却具有静态生存期,比如上面代码示例中的“someLocalStaticVariable”。这种情况下,常见的非正式命名方式会变得非常混乱。例如,当我们说“本地变量”时,通常指的是“本地范围自动分配的变量”,而当我们说全局变量时,通常指的是“全局范围静态分配的变量”。不幸的是,对于诸如“文件范围静态分配的变量”之类的事物,许多人只会说......“啥???”。C/C++ 中的某些语法选择加剧了这个问题 - 例如,由于下面所示的语法,许多人认为全局变量不是“静态”的。
int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation
int main() {return 0;}
注意,在上面的声明中放置关键字“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
PostScript
这样的一些语言具有多个堆栈,但具有更像堆栈的行为的“堆”。 - supercat栈 当你调用一个函数时,该函数的参数以及一些其他开销被放置在栈中。还会存储一些信息(例如返回地址)。 当你在函数内部声明一个变量时,该变量也会被分配在栈上。
释放栈非常简单,因为总是按照分配的相反顺序进行释放。随着进入函数,栈空间增加,退出函数时对应的数据被移除。这意味着你倾向于在栈的一个小区域内操作,除非你调用了很多调用其他函数的函数(或创建递归解决方案)。
堆 堆是一个通用名称,用于存放在程序运行期间创建的数据。如果你不知道你的程序将创建多少太空船,你可能会使用new(或malloc或等效)运算符来创建每个太空船。这种分配可能要持续一段时间,因此我们释放它们的顺序可能与创建它们的顺序不同。
因此,堆远比栈复杂,因为有未使用的内存区域与被碎片化的内存块交错。查找所需大小的空闲内存是一个困难的问题。这就是为什么应该避免使用堆(尽管它仍经常被使用)。
实现 栈和堆的实现通常由运行时/操作系统处理。通常,对于关键性能的游戏和其他应用程序会创建自己的内存解决方案,从堆中获取大块内存,然后在内部进行分配以避免依赖操作系统的内存。
只有当你的内存使用情况与常规情况非常不同(例如,在游戏中,你一次加载一个级别并可以在另一个巨大的操作中丢弃整个级别时),这才是实际可行的。
物理内存位置
由于一种名为虚拟内存的技术存在,所以此内容并不像您想象中那么相关。该技术使您的程序认为您可以访问某个地址,而实际上物理数据在其他地方(甚至在硬盘上!)。随着调用树的加深,您获得的堆栈地址会按递增顺序排列。堆的地址是不可预测的(即实现特定),并且实际上并不重要。其他人已经回答了大致情况,所以我会补充一些细节。
堆栈并不一定是单数。如果进程中有多个线程,则通常情况下会有多个堆栈。你也可以拥有多个堆,例如某些DLL配置可能导致不同的DLL从不同的堆中分配内存,这就是为什么释放由不同库分配的内存通常是一个坏主意。
在C语言中,你可以通过使用alloca来获得可变长度分配的好处,它在栈上分配,而不是在堆上分配。这段内存不会在函数返回后保留,但对于临时缓冲区非常有用。
在Windows上制作一个你几乎不使用的巨大临时缓冲区并不是免费的。这是因为编译器会生成一个堆栈探测循环,在每次进入函数时调用它,以确保堆栈存在(因为Windows在堆栈末尾使用单个卫兵页来检测何时需要增加堆栈。如果你访问比堆栈末尾超过一个页面的内存,你将会崩溃)。例如:
void myfunction()
{
char big[10000000];
// Do something that only uses for first 1K of big 99% of the time.
}
其他人已经直接回答了你的问题,但是当试图理解堆栈和堆时,我认为考虑传统UNIX进程(没有线程和基于mmap()
分配器)的内存布局很有帮助。 Memory Management Glossary这个网页上有一张这种内存布局的图示。
传统上,堆栈和堆位于进程的虚拟地址空间的两端。堆栈在访问时会自动增长,直到由内核设置的大小(可以使用setrlimit(RLIMIT_STACK, ...)
进行调整)。当内存分配器调用brk()
或sbrk()
系统调用将更多页面的物理内存映射到进程的虚拟地址空间中时,堆会增长。
在没有虚拟内存的系统中,例如某些嵌入式系统,相同的基本布局通常适用,除了堆栈和堆的大小是固定的。然而,在其他嵌入式系统(如基于Microchip PIC微控制器的系统)中,程序堆栈是一个单独的内存块,不能通过数据移动指令寻址,只能通过程序流程指令(call,return等)间接修改或读取。其他架构,例如Intel Itanium处理器,具有多个堆栈。从这个意义上说,堆栈是CPU架构的一个元素。
什么是栈?
栈是一堆通常整齐排列的物品。
在计算机架构中,栈是以后进先出方式添加或移除数据的内存区域。
在多线程应用程序中,每个线程将有其自己的栈。
什么是堆?
堆是一个零散无序地堆放着东西的集合。
在计算机架构中,堆是由操作系统或内存管理库自动管理的动态分配的内存区域。
堆上的内存在程序执行期间经常被分配、释放和调整大小,这可能导致一种称为“碎片化”的问题。
碎片化发生在内存对象之间分配了太小的空间,无法容纳其他内存对象的情况下。
结果是一定比例的堆空间不能用于进一步的内存分配。
两者如何共存?
在多线程应用程序中,每个线程将有其自己的栈。但是,所有不同的线程将共享堆。
因为不同的线程在多线程应用程序中共享堆,这也意味着它们必须协调以避免同时尝试访问和操作堆中相同的内存段。
哪一个更快——栈还是堆?为什么?
栈比堆快得多。
这是因为栈上的内存分配方式。
在栈上分配内存就像将栈指针向上移动一样简单。
对于新手来说,使用栈可能是一个不错的选择,因为它更容易操作。
由于栈的空间比较小,所以当你知道需要使用多少内存来存储数据,或者你的数据量很小时,可以考虑使用栈。
而如果你需要大量内存来存储数据,或者你不确定需要多少内存(例如使用动态数组),则最好使用堆。
栈是存储局部变量(包括方法参数)的内存区域。对于对象变量,它们只是指向堆上实际对象的引用(指针)。
每次实例化一个对象时,都会在堆内存中预留一段空间来存储该对象的数据(状态)。因为对象可能包含其他对象,因此这些数据实际上可能包含对这些嵌套对象的引用。
rlimit_stack
等系统变量和行为的某些方面。另请参见 Red Hat 的 Issue 1463241。 - jww