堆栈和堆的内容和位置是什么?

时间:2008-09-17 04:18:06

标签: memory-management stack language-agnostic heap dynamic-memory-allocation

编程语言书籍解释了在堆栈上创建了值类型,并且在上创建了引用类型,而没有解释这两者是什么。我还没有看清楚这个问题。我理解堆栈是什么。但是,

  • 他们在哪里以及他们是什么(身体在真实计算机的记忆中)?
  • 它们在多大程度上受操作系统或语言运行时控制?
  • 他们的范围是什么?
  • 是什么决定了每个人的身材?
  • 是什么让一个人更快?

29 个答案:

答案 0 :(得分:5630)

堆栈是作为执行线程的临时空间留出的内存。调用函数时,会在堆栈顶部为区域变量和一些簿记数据保留一个块。当该函数返回时,该块将变为未使用状态,并可在下次调用函数时使用。堆栈始终以LIFO(后进先出)顺序保留;最近保留的块始终是要释放的下一个块。这使得跟踪堆栈非常简单;从堆栈中释放一个块只不过是调整一个指针。

堆是为动态分配留出的内存。与堆栈不同,堆中的块的分配和释放没有强制模式;您可以随时分配一个块并随时释放它。这使得在任何给定时间跟踪堆的哪些部分被分配或释放变得更加复杂;有许多自定义堆分配器可用于调整不同使用模式的堆性能。

每个线程获得一个堆栈,而应用程序通常只有一个堆(尽管为不同类型的分配设置多个堆并不罕见)。

直接回答您的问题:

  

它们在多大程度上受OS或语言运行时控制?

操作系统在创建线程时为每个系统级线程分配堆栈。通常,语言运行库会调用OS来为应用程序分配堆。

  

他们的范围是什么?

堆栈附加到一个线程,因此当线程退出堆栈时会被回收。堆通常在应用程序启动时由运行时分配,并在应用程序(技术过程)退出时回收。

  

是什么决定了每个人的大小?

创建线程时设置堆栈的大小。堆的大小在应用程序启动时设置,但可以在需要空间时增长(分配器从操作系统请求更多内存)。

  

是什么让一个人更快?

堆栈更快,因为访问模式使得从中分配和释放内存变得微不足道(指针/整数简单地递增或递减),而堆在分配或释放中涉及更复杂的簿记。此外,堆栈中的每个字节都经常被频繁地重用,这意味着它往往被映射到处理器的缓存,使其非常快。堆的另一个性能影响是堆(主要是全局资源)通常必须是多线程安全的,即每个分配和释放需要 - 通常 - 与程序中的“所有”其他堆访问同步。 / p>

明确的示范:
图像来源:vikashazrati.wordpress.com

答案 1 :(得分:2232)

<强>堆栈:

  • 就像堆一样存储在计算机RAM中。
  • 在堆栈上创建的变量将超出范围并自动解除分配。
  • 与堆上的变量相比,分配的速度要快得多。
  • 使用实际的堆栈数据结构实现。
  • 存储本地数据,返回地址,用于参数传递。
  • 当使用过多的堆栈时(主要来自无限或太深的递归,非常大的分配),堆栈可能会溢出。
  • 可以在没有指针的情况下使用在堆栈上创建的数据。
  • 如果您确切地知道在编译时需要分配多少数据并且它不是太大,那么您将使用堆栈。
  • 通常在程序启动时已确定最大大小。

<强>堆

  • 就像堆栈一样存储在计算机RAM中。
  • 在C ++中,必须手动销毁堆上的变量,并且永远不会超出范围。数据已使用deletedelete[]free
  • 释放
  • 与堆栈中的变量相比,分配速度较慢。
  • 根据需要用于分配程序使用的数据块。
  • 当存在大量分配和解除分配时,可能会出现碎片。
  • 在C ++或C中,堆上创建的数据将由指针指向,并分别用newmalloc分配。
  • 如果请求分配的缓冲区太大,可能会出现分配失败。
  • 如果您不确切知道运行时需要多少数据,或者需要分配大量数据,则可以使用堆。
  • 负责内存泄漏。

示例:

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;

答案 2 :(得分:1320)

答案 3 :(得分:693)

答案 4 :(得分:386)

在以下C#代码中

public void Method1()
{
    int i = 4;
    int y = 2;
    class1 cls1 = new class1();
}

以下是如何管理内存

Picture of variables on the stack

Local Variables,只要函数调用进入堆栈,只需要持续。堆用于变量,我们事先并不知道它们的生命周期,但我们希望它们可以持续一段时间。在大多数语言中,如果我们想要将它存储在堆栈中,那么在编译时我们知道变量有多大是至关重要的。

对象(在更新它们时大小不同)会在堆上进行,因为我们在创建时不知道它们将持续多长时间。在许多语言中,堆被垃圾收集以查找不再具有任何引用的对象(例如cls1对象)。

在Java中,大多数对象直接进入堆。在像C / C ++这样的语言中,当你不处理指针时,结构和类通常可以保留在堆栈中。

可在此处找到更多信息:

The difference between stack and heap memory allocation « timmurphy.org

在这里:

Creating Objects on the Stack and Heap

本文是上图的来源:Six important .NET concepts: Stack, heap, value types, reference types, boxing, and unboxing - CodeProject

但请注意,它可能包含一些不准确之处。

答案 5 :(得分:196)

筹码 当你调用一个函数时,该函数的参数加上一些其他开销被放在堆栈上。一些信息(例如返回的地方)也存储在那里。 在函数内部声明变量时,该变量也会在堆栈中分配。

取消分配堆栈非常简单,因为您总是按照分配的相反顺序解除分配。输入函数时会添加堆栈内容,退出时会删除相应的数据。这意味着除非你调用许多调用许多其他函数的函数(或创建一个递归解决方案),否则你倾向于留在堆栈的一个小区域内。

堆是一个通用名称,用于放置您即时创建的数据。如果您不知道程序将创建多少太空飞船,您可能会使用新的(或malloc或等效的)运算符来创建每个太空飞船。这种分配将持续一段时间,因此我们很可能会以与创建它们不同的顺序释放事物。

因此,堆要复杂得多,因为最终会出现未使用的内存区域与内存被分段的内存交错。找到所需大小的空闲内存是一个难题。这就是应该避免堆的原因(虽然它仍然经常使用)。

<强>实施 堆栈和堆的实现通常都是运行时/操作系统。通常,性能至关重要的游戏和其他应用程序会创建自己的内存解决方案,从堆中获取大量内存,然后在内部将其清除,以避免依赖操作系统获取内存。

这只有在你的内存使用量与标准大不相同的情况下才是实用的 - 例如,你在一个巨大的操作中加载一个级别的游戏,并且可以在另一个巨大的操作中丢掉所有内容。

内存中的物理位置 由于名为Virtual Memory的技术使您的程序认为您可以访问物理数据位于其他位置的某个地址(即使在硬盘上!),因此这与您的想法相关性较小。当您的调用树变得更深时,您获得的堆栈地址会逐渐增加。堆的地址是不可预测的(即特定的实施),坦率地说并不重要。

答案 6 :(得分:178)

澄清一下,this answer有错误的信息(thomas在评论后修复了他的答案,很酷:))。其他答案只是避免解释静态分配的含义。因此,我将解释三种主要的分配形式以及它们通常如何与下面的堆,堆栈和数据段相关联。我还将在C / C ++和Python中展示一些示例,以帮助人们理解。

“静态”(AKA静态分配)变量未在堆栈上分配。不要这么认为 - 许多人只是因为“静态”听起来很像“堆叠”。它们实际上既不存在于堆栈中,也不存在于堆中。这是所谓的data segment

的一部分

但是,通常最好考虑“范围”和“生存期”而不是“堆叠”和“堆”。

范围是指代码的哪些部分可以访问变量。通常我们会想到本地范围(只能通过当前函数访问)与全局范围(可以在任何地方访问),尽管范围可能会变得更加复杂。

生命周期是指在程序执行期间分配和解除分配变量的时间。通常我们会想到静态分配(变量将在程序的整个持续时间内持续存在,使其在多个函数调用中存储相同的信息非常有用)与自动分配(变量)仅在单次调用函数期间持续存在,使其对存储仅在函数期间使用的信息很有用,并且一旦完成就可以丢弃)与动态分配(持续时间在运行时定义的变量) ,而不是像静态或自动的编译时间。)

虽然大多数编译器和解释器在使用堆栈,堆等方面类似地实现了这种行为,但只要行为正确,编译器有时可能会破坏这些约定。例如,由于优化,局部变量可能只存在于寄存器中或被完全删除,即使堆栈中存在大多数局部变量。正如在一些注释中指出的那样,你可以自由地实现一个甚至不使用堆栈或堆的编译器,而是使用其他一些存储机制(很少做,因为堆栈和堆很适合这个)。 / p>

我将提供一些简单的带注释的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”。这些变量可以使我们共同但非正式的命名习惯非常混乱。例如,当我们说“ local ”时,我们通常表示“本地范围自动分配变量”,当我们说全局时,我们通常表示“全局范围的静态分配变量< / EM>”。不幸的是,当涉及到“文件范围的静态分配变量”之类的东西时,很多人只会说......“呵呵??? ”。

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

答案 7 :(得分:161)

其他人已经很好地回答了广泛的笔触,所以我会提出一些细节。

  1. 堆栈和堆不必是单数。如果您在一个进程中有多个线程,那么您有多个堆栈的常见情况。在这种情况下,每个线程都有自己的堆栈。您也可以拥有多个堆,例如某些DLL配置可能会导致从不同的堆分配不同的DLL,这就是为什么释放由不同库分配的内存通常是个坏主意。

  2. 在C中,您可以通过使用alloca来获得可变长度分配的好处,{{3}}分配在堆栈上,而不是在堆上分配的alloc。这个内存不会在你的return语句中存活,但它对临时缓冲区很有用。

  3. 在Windows上制作一个巨大的临时缓冲区并不是免费的。这是因为编译器将生成每次输入函数时调用的堆栈探测循环,以确保堆栈存在(因为Windows使用堆栈末尾的单个防护页面来检测何时需要增加堆栈。如果你从堆栈的末尾访问多个页面的内存,你将崩溃)。例如:

  4. void myfunction()
    {
       char big[10000000];
       // Do something that only uses for first 1K of big 99% of the time.
    }
    

答案 8 :(得分:128)

其他人直接回答了你的问题,但在尝试理解堆栈和堆时,我认为考虑传统UNIX进程的内存布局(没有线程和基于mmap()的分配器)是有帮助的。 Memory Management Glossary网页上有一个内存布局图。

堆栈和堆传统上位于进程虚拟地址空间的两端。堆栈在访问时会自动增长,最大可达内核设置的大小(可以使用setrlimit(RLIMIT_STACK, ...)进行调整)。当内存分配器调用brk()sbrk()系统调用时,堆会增长,将更多页面的物理内存映射到进程的虚拟地址空间。

在没有虚拟内存的系统中,例如某些嵌入式系统,通常会应用相同的基本布局,但堆栈和堆的大小是固定的。但是,在其他嵌入式系统(例如基于Microchip PIC单片机的系统)中,程序堆栈是一个单独的内存块,无法通过数据移动指令寻址,只能通过程序流指令间接修改或读取(调用,返回等)。其他架构(如Intel Itanium处理器)具有multiple stacks。从这个意义上说,堆栈是CPU架构的一个元素。

答案 9 :(得分:109)

堆栈是内存的一部分,可以通过几个关键的汇编语言指令来操作,例如'pop'(从堆栈中删除并返回一个值)和'push'(将值推送到堆栈),但是还调用(调用子程序 - 这会将地址推回到堆栈中)并返回(从子程序返回 - 这会将地址弹出堆栈并跳转到它)。它是堆栈指针寄存器下面的内存区域,可以根据需要进行设置。堆栈还用于将参数传递给子例程,也用于在调用子例程之前保留寄存器中的值。

堆是操作系统给应用程序的内存的一部分,通常通过类似malloc的系统调用。在现代操作系统上,此内存是一组只有调用进程才能访问的页面。

堆栈的大小在运行时确定,并且通常在程序启动后不会增长。在C程序中,堆栈需要足够大以容纳每个函数中声明的每个变量。堆将根据需要动态增长,但操作系统最终会进行调用(它通常会使堆的增长超过malloc请求的值,因此至少某些未来的malloc将不需要返回到内核获得更多内存。此行为通常是可自定义的)

因为你在启动程序之前已经分配了堆栈,所以在使用堆栈之前你永远不需要malloc,所以那里有一点点优势。在实践中,很难预测具有虚拟内存子系统的现代操作系统的速度和速度会有多快,因为页面的实现方式和存储位置是实现细节。

答案 10 :(得分:108)

我认为很多其他人在这件事上给了你大部分正确答案。

然而,遗漏的一个细节是“堆”实际上可能被称为“免费商店”。这种区别的原因是原始的免费存储是使用称为“二项式堆”的数据结构实现的。出于这个原因,从malloc()/ free()的早期实现中分配是从堆中分配的。然而,在这个现代的日子里,大多数免费商店都是用非常复杂的数据结构实现的,而不是二项式堆。

答案 11 :(得分:104)

什么是筹码?

堆栈是一堆物体,通常是整齐排列的物体。

Enter image description here

  
    

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

  

什么是堆?

堆是随意堆积的不整齐的东西。

Enter image description here

  
    

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

  

两者一起

  
    

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

  

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

  
    

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

  

对于刚接触编程的人来说,使用堆栈可能是一个好主意,因为它更容易。
由于堆栈很小,因此当您确切知道数据需要多少内存时,或者如果您知道数据的大小非常小时,则需要使用它。
当您知道数据需要大量内存时,最好使用堆,或者您不确定需要多少内存(例如动态数组)。

Java内存模型

Enter image description here

堆栈是存储局部变量(包括方法参数)的内存区域。对于对象变量,这些仅仅是对堆上实际对象的引用(指针) 每次实例化一个对象时,都会留出一大堆堆内存来保存该对象的数据(状态)。由于对象可以包含其他对象,因此某些数据实际上可以保存对这些嵌套对象的引用。

答案 12 :(得分:87)

你可以用堆栈做一些有趣的事情。例如,你有像alloca这样的函数(假设你可以通过关于它的使用的大量警告),这是一种malloc,它专门使用堆栈而不是堆来存储。

也就是说,基于堆栈的内存错误是我经历过的最糟糕的错误。如果使用堆内存,并且超出了已分配块的范围,则有可能触发段错误。 (不是100%:你的块可能偶然与你先前分配的另一个块连续。)但是由于在堆栈上创建的变量总是相互连续,因此写出越界可以改变另一个变量的值。我了解到每当我觉得我的程序已经停止服从逻辑定律时,它可能就是缓冲区溢出。

答案 13 :(得分:84)

简单地说,堆栈是创建局部变量的地方。此外,每次调用子程序时,程序计数器(指向下一个机器指令的指针)和任何重要的寄存器,有时参数都会被压入堆栈。然后子程序中的任何局部变量被压入堆栈(并从那里使用)。当子程序结束时,所有东西都会从堆栈中弹出。 PC和寄存器数据会在弹出时放回原处,因此您的程序可以顺利进行。

堆是内存区域动态内存分配由(显式“新”或“分配”调用)组成。它是一种特殊的数据结构,可以跟踪不同大小的内存块及其分配状态。

在“经典”系统中,RAM的布局使得堆栈指针从内存底部开始,堆指针从顶部开始,并且它们相互增长。如果它们重叠,则表示RAM不足。但这不适用于现代多线程操作系统。每个线程都必须有自己的堆栈,这些堆栈可以动态创建。

答案 14 :(得分:79)

来自WikiAnwser。

堆栈

当一个函数或一个方法调用另一个函数,该函数又调用另一个函数等时,所有这些函数的执行将保持暂停状态,直到最后一个函数返回其值。

这个挂起函数调用链是堆栈,因为堆栈中的元素(函数调用)彼此依赖。

在异常处理和线程执行中需要考虑堆栈。

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

答案 15 :(得分:50)

<强>堆栈

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

<强>堆

  • 可以全局访问变量
  • 内存大小无限制
  • (相对)慢访问
  • 无法保证有效利用空间,随着内存块的分配,内存可能会随着时间的推移而分散,然后释放
  • 你必须管理记忆(你负责分配和释放变量)
  • 可以使用realloc()
  • 调整变量大小

答案 16 :(得分:42)

好的,简单而言之,他们的意思是有序未订购 ......!

堆栈:在堆叠项目中,事物处于彼此的顶部,意味着处理更快,更高效!...

因此总有一个索引指向特定项目,处理速度也会更快,项目之间也存在关联!...

:没有订单,处理速度会变慢,价值会混乱而没有特定的订单或索引......有随机且它们之间没有任何关系...所以执行和使用时间可能会有所不同......

我还创建了下面的图片,以显示它们的外观:

enter image description here

答案 17 :(得分:39)

简而言之

堆栈用于静态内存分配,堆用于动态内存分配,两者都存储在计算机的RAM中。

详细信息

筹码

堆栈是&#34; LIFO&#34; (后进先出)数据结构,由CPU非常密切地管理和优化。每当一个函数声明一个新变量时,它就会被推送到#34;到堆栈上。然后每次函数退出时,该函数推送到堆栈的所有变量都被释放(也就是说,它们被删除)。一旦释放了堆栈变量,该内存区域就可用于其他堆栈变量。

使用堆栈存储变量的优点是可以为您管理内存。你不必手动分配记忆,或者一旦你不再需要它就把它释放出来。更重要的是,因为CPU如此高效地组织堆栈内存,读取和写入堆栈变量的速度非常快。

可以找到更多 here

堆是计算机内存的一个区域,不会自动为您管理,并且不受CPU的严格管理。它是一个更自由浮动的内存区域(并且更大)。要在堆上分配内存,必须使用malloc()或calloc(),它们是内置的C函数。一旦你在堆上分配了内存,你就有责任使用free()在你不再需要它时解除分配内存。

如果您没有这样做,您的程序将会出现所谓的内存泄漏。也就是说,堆上的内存仍然会被搁置(并且不会被其他进程使用)。正如我们将在调试部分中看到的,有一个名为Valgrind的工具可以帮助您检测内存泄漏。

与堆栈不同,堆对可变大小没有大小限制(除了计算机明显的物理限制)。堆内存的读取和写入速度稍慢,因为必须使用指针来访问堆上的内存。我们很快就会讨论指针。

与堆栈不同,堆上创建的变量可由程序中任何位置的任何函数访问。堆变量本质上是全局的。

可以找到更多 here

在堆栈上分配的变量直接存储到存储器中,对该存储器的访问非常快,并且在编译程序时处理其分配。当函数或方法调用另一个函数,该函数又调用另一个函数等时,所有这些函数的执行将保持挂起,直到最后一个函数返回其值。堆栈始终以LIFO顺序保留,最近保留的块始终是要释放的下一个块。这使得跟踪堆栈非常简单,从堆栈中释放块只不过是调整一个指针。

在堆上分配的变量在运行时分配了内存并且访问此内存有点慢,但堆大小仅受虚拟内存大小的限制。堆的元素彼此之间没有依赖关系,并且可以随时随机访问。您可以随时分配一个块并随时释放它。这使得在任何给定时间跟踪堆的哪些部分被分配或释放变得更加复杂。

Enter image description here

如果您确切地知道在编译之前需要分配多少数据,那么您可以使用堆栈,并且它不会太大。如果您不确切知道运行时需要多少数据,或者需要分配大量数据,则可以使用堆。

在多线程情况下,每个线程都有自己完全独立的堆栈,但它们将共享堆。堆栈是特定于线程的,堆是特定于应用程序的。在异常处理和线程执行中,堆栈很重要。

每个线程都有一个堆栈,而应用程序通常只有一个堆(尽管不同类型的分配有多个堆并不常见)。

Enter image description here

在运行时,如果应用程序需要更多堆,它可以从可用内存中分配内存,如果堆栈需要内存,它可以从应用程序的空闲内存分配内存中分配内存。

甚至,更详细的信息是herehere

现在来你的问题的答案

  

它们在多大程度上受操作系统或语言运行时控制?

操作系统在创建线程时为每个系统级线程分配堆栈。通常,语言运行库会调用OS来为应用程序分配堆。

可以找到更多 here

  

他们的范围是什么?

已经在顶部给出。

  

&#34;如果您确切地知道在编译之前需要分配多少数据,那么您可以使用堆栈,并且它不会太大。如果您不确切知道运行时需要多少数据,或者需要分配大量数据,则可以使用堆。&#34;

更多信息可以在here找到。

  

是什么决定了每个人的规模?

创建线程时,堆栈的大小由OS设置。堆的大小是在应用程序启动时设置的,但它可以在需要空间时增长(分配器从操作系统请求更多内存)。

  

是什么让一个人更快?

堆栈分配要快得多,因为它实际上只是移动堆栈指针。使用内存池,您可以从堆分配中获得可比较的性能,但这会带来轻微的复杂性和自身的麻烦。

此外,堆栈与堆不仅是性能考虑因素;它还告诉你很多关于对象的预期寿命。

详细信息可以从 here 找到。

答案 18 :(得分:35)

在20世纪80年代,UNIX像兔子一样传播,大公司自己推销。 埃克森只有一个,历史上失去了数十个品牌。 如何记忆是由许多实施者自行决定的。

典型的C程序在内存中平放 通过改变brk()值来增加的机会。 通常,HEAP刚好低于此brk值 并且增加brk会增加可用堆的数量。

单个STACK通常是HEAP以下的一个区域,它是一段记忆 直到下一个固定内存块的顶部才包含任何有价值的内容。 下一个块通常是CODE,可以被堆栈数据覆盖 在其时代的一个着名的黑客中。

一个典型的内存块是BSS(一个零值块) 在一家制造商的产品中意外没有归零。 另一个是包含初始化值的DATA,包括字符串和数字。 第三个是包含CRT(C运行时),主要,函数和库的CODE。

UNIX中虚拟内存的出现改变了许多约束。 这些块需要连续的没有客观原因, 或固定尺寸,或现在订购特定方式。 当然,在UNIX之前,Multics并没有受到这些限制。 这是一个显示那个时代的存储器布局之一的示意图。

A typical 1980s style UNIX C program memory layout

答案 19 :(得分:29)

虚拟内存中每个进程的

堆栈数据

stack, heap and static data

答案 20 :(得分:24)

几分钱:我认为,绘制内存图形会更简单:

This is my vision of process memory construction with simplification for more easy understanding wht happening


箭头 - 显示增长堆栈和堆的位置,进程堆栈大小有限制,在OS中定义,线程堆栈大小通常由线程创建API中的参数限制。堆通常限制为进程最大虚拟内存大小,例如32位2-4 GB。

这么简单的方法:进程堆对于进程和内部的所有线程都是通用的,在常见情况下用于内存分配,例如 malloc()

Stack是快速内存,用于存储常见情况函数返回指针和变量,作为函数调用中的参数处理,本地函数变量。

答案 21 :(得分:21)

由于一些答案是挑剔的,我会贡献我的螨虫。

令人惊讶的是,没有人提到多个(即与运行的OS级线程的数量无关)调用堆栈不仅可以在外来语言(PostScript)或平台(Intel Itanium)中找到,而且还可以在{ {3}},fibers以及green threads的一些实现。

纤维,绿线和协程在很多方面都很相似,这导致很多混乱。光纤和绿色线程之间的区别在于前者使用协作式多任务处理,而后者可能具有协作式或抢占式(或甚至两者)。有关纤维和协同程序之间的区别,请参阅coroutines

在任何情况下,光纤,绿色线程和协同程序的目的是同时执行多个函数,但在单个操作系统中并行(参见here区分) - 线程,以有组织的方式来回传递控制。

使用光纤,绿色线程或协同程序时,通常每个函数都有一个单独的堆栈。 (从技术上讲,不仅仅是一个堆栈,而是整个执行上下文是每个函数。最重要的是,CPU寄存器。)对于每个线程,存在与并发运行函数一样多的堆栈,并且线程在执行每个函数之间切换根据你的程序的逻辑。当函数运行到其末尾时,其堆栈将被销毁。因此,堆栈的数量和生命周期是动态的,不是由OS级别线程的数量决定的!

请注意,我说“通常每个函数都有一个单独的堆栈”。有一些 stackful 无堆栈的couroutines实现。最值得注意的堆栈C ++实现是this SO questionBoost.Coroutineasync/await。 (但是,向C ++ 17提出的C ++的Microsoft PPL(a.k.a。“asyncawait”)可能会使用无堆栈协程。)

纤维对C ++标准库的提议即将发布。此外,还有一些第三方resumable functions。绿色线程在Python和Ruby等语言中非常流行。

答案 22 :(得分:14)

我有一些东西可以分享,虽然已经涵盖了主要观点。

堆叠

  • 非常快速的访问。
  • 存储在RAM中。
  • 此处加载函数调用以及传递的局部变量和函数参数。
  • 当程序超出范围时,会自动释放空格。
  • 存储在顺序存储器中。

<强>堆

  • 相对于Stack而言访问速度较慢。
  • 存储在RAM中。
  • 动态创建的变量存储在此处,以后需要在使用后释放分配的内存。
  • 存储内容分配的存储位置,始终由指针访问。

有趣的提示:

  • 如果函数调用已存储在堆中,则会产生2个混乱点:
    1. 由于堆栈中的顺序存储,执行速度更快。堆中的存储会导致大量的时间消耗,从而使整个程序执行得更慢。
    2. 如果函数存储在堆中(指针指向凌乱的存储空间),则无法返回到调用者地址(由于内存中的顺序存储,该堆栈会给出)。

答案 23 :(得分:8)

许多答案都是正确的概念,但我们必须注意硬件(即微处理器)需要一个堆栈来允许调用子程序(汇编语言中的CALL ......)。 (OOP人会称之为方法

在堆栈上保存返回地址并调用→push / ret→pop直接在硬件中管理。

您可以使用堆栈传递参数..即使它比使用寄存器慢(微处理器大师会说或者是20世纪80年代的好BIOS书......)

  • 没有堆栈没有微处理器可以工作。 (我们无法想象一个程序,即使是汇编语言,没有子程序/函数)
  • 没有堆就可以。 (汇编语言程序可以在没有,因为堆是操作系统概念,而不是malloc,即OS / Lib调用。

堆栈使用速度更快:

  • 硬件,甚至推/弹都非常有效。
  • malloc需要进入内核模式,使用锁定/信号量(或其他同步原语)执行某些代码并管理跟踪分配所需的一些结构。

答案 24 :(得分:8)

哇!这么多答案,我不认为其中一个答案正确...

1)它们在哪里和什么(物理上在真实计算机的内存中)?

堆栈是从分配给程序映像的最高内存地址开始的内存,然后从那里开始递减值。它保留给称为函数的参数以及函数中使用的所有临时变量。

有两堆:公共堆和私有堆。

私有堆从程序中代码的最后一个字节之后的16字节边界(对于64位程序)或8字节边界(对于32位程序)开始,然后从那里开始增加值。也称为默认堆。

如果私有堆太大,则它将与堆栈区域重叠;如果私有堆太大,则堆栈将与堆栈重叠。由于堆栈从较高的地址开始,然后一直向下移动至较低的地址,因此通过适当的破解,您可以使堆栈变大,以至于溢出专用堆区域并重叠代码区域。然后,诀窍是要重叠足够的代码区域,以便可以将其连接到代码中。这样做有些棘手,而且您可能会遇到程序崩溃的风险,但这既简单又有效。

公共堆位于程序映像空间之外的自己的内存空间中。如果内存资源稀缺,正是这种内存将被虹吸到硬盘上。

2)它们在多大程度上受操作系统或语言运行时的控制?

堆栈是由程序员控制的,私有堆是由OS管理的,而公共堆是不受任何人控制的,因为它是OS服务-发出请求,然后授予或拒绝请求。

2b)他们的范围是什么?

它们对于程序都是全局的,但是它们的内容可以是私有的,公共的或全局的。

2c)是什么决定了它们的大小?

堆栈和专用堆的大小由编译器运行时选项确定。公共堆是在运行时使用size参数初始化的。

2d)是什么使速度更快?

它们并不是为了快速而设计的,而是被设计为有用的。程序员如何利用它们来确定它们是“快速”还是“慢速”

REF:

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-heapcreate

答案 25 :(得分:6)

堆是由操作系统或内存管理器库自动管理的动态分配内存区域。您可以随时分配一个块,并随时释放它。堆分配需要完整记录分配的内存和未分配的内存,并进行一些开销维护以减少碎片,找到足够大以适合请求的大小的连续内存段,依此类推。可以随时释放内存以留下可用空间。随着堆的增长,通常将新块从低地址分配到高地址。因此,您可以将堆视为存储块的堆,随着分配的内存,存储块的大小会增加。如果堆对于分配而言太小,则通常可以通过从底层操作系统获取更多内存来增加大小。从堆分配的内存将保持分配状态,直到发生以下情况之一:

  • 内存已释放
  • 程序终止

堆栈

  • 就像堆一样存储在计算机RAM中。
  • 在堆栈上创建的变量将超出范围并自动释放。
  • 与堆中的变量相比,分配速度要快得多。
  • 存储用于参数传递的本地数据,返回地址。
  • 使用过多堆栈(大多数情况下,可能会导致堆栈溢出) 来自无限或太深的递归,分配非常大。
  • 如果您确切知道需要多少数据,则可以使用堆栈 在编译时间之前分配,并且不会太大。
  • 通常在您的程序确定时具有最大大小 开始。

堆:

  • 像堆栈一样存储在计算机RAM中。
  • 在C ++中,必须手动销毁堆上的变量,并且永远不要 超出范围。
  • 使用delete,delete []或free释放数据。
  • 与堆栈中的变量相比,分配速度较慢。
  • 按需使用以分配数据块以供程序使用。
  • 当分配很多时,可能会有碎片 释放。
  • 在C ++或C中,在堆上创建的数据将由指针指向 并分别使用new或malloc分配。
  • 如果请求太大的缓冲区来分配可能会失败 被分配。
  • 如果您不确切知道要使用多少数据,则可以使用堆 在运行时或需要分配大量数据时将需要。
  • 负责内存泄漏。

答案 26 :(得分:2)

堆栈本质上是一个易于访问的内存,仅管理其项目 作为一个很好的堆栈。只有预先知道尺寸的物品才能进入堆栈。数字,字符串,布尔值就是这种情况。

堆用于存储您无法预先确定的项目 确切的大小和结构。由于对象和数组可以被突变和 在运行时进行更改,它们必须进入堆。

来源:Academind

答案 27 :(得分:1)

CPU堆栈和堆在物理上与CPU和寄存器与内存的工作方式,机器汇编语言的工作方式有关,而不与高级语言本身有关,即使这些语言可以决定很小的事情。

所有现代CPU都遵循“相同”微处理器理论:它们全部基于所谓的“寄存器”,而有些则用于“堆栈”以获得性能。从一开始,所有CPU都有堆栈寄存器,据我所知,它们一直都在这里。汇编语言从一开始就一样,尽管有所不同...直到Microsoft及其中间语言(IL)才改变了范例,使其具有OO虚拟机汇编语言。因此,将来我们将能够拥有一些CLI / CIL CPU(MS的一个项目)。

CPU具有堆栈寄存器以加快内存访问速度,但是与使用其他寄存器以获得对进程的所有可用内存的完全访问权限相比,它们受到限制。这就是为什么我们讨论堆栈和堆分配的原因。

总而言之,总的来说,堆是缓慢且缓慢的,并且用于“全局”实例和对象的内容,因为堆栈又小又快,并且用于“局部”变量和引用(隐藏的指针会忘记管理它们) )。

因此,当我们在方法中使用new关键字时,将在堆栈中创建引用(一个int),但是如果在堆中创建了对象及其所有内容(值类型和对象),记得。但是局部基本值类型和数组是在堆栈中创建的。

内存访问的区别在于单元引用级别:寻址堆(进程的整体内存)在处理CPU寄存器方面需要更多的复杂性,而堆栈在寻址方面在本地“更多”,因为我记得,CPU堆栈寄存器用作基地址。

这就是为什么当我们进行非常长或无限次的递归调用或循环时,我们很快就会发生堆栈溢出,而不会冻结现代计算机上的系统...

C# Heap(ing) Vs Stack(ing) In .NET

Stack vs Heap: Know the Difference

Static class memory allocation where it is stored C#

What and where are the stack and heap?

https://en.wikipedia.org/wiki/Memory_management

https://en.wikipedia.org/wiki/Stack_register

汇编语言资源:

Assembly Programming Tutorial

Intel® 64 and IA-32 Architectures Software Developer Manuals

答案 28 :(得分:0)

感谢您进行了非常好的讨论,但是作为一个真正的菜鸟,我想知道将说明保存在哪里?在开始阶段,科学家们在两种架构之间做出决定(冯·纽曼(von NEUMANN)将所有内容都视为数据,而哈佛(HARVARD)则将内存区域用于指令,而另一区域则用于数据)。最终,我们采用了冯·诺依曼(von Neumann)的设计,现在一切都被认为是“相同的”。当我学习汇编时,这使我很难 https://www.cs.virginia.edu/~evans/cs216/guides/x86.html 因为他们谈论寄存器和堆栈指针。

以上所有内容都谈到了DATA。我的猜测是,由于指令是具有特定内存占用空间的已定义事物,因此它将进入堆栈,因此汇编中讨论的所有“那些”寄存器都位于堆栈中。当然,然后是面向对象的程序设计,其中指令和数据混合成一个动态的结构,所以现在指令也将保留在堆中?