关于内存管理:堆栈和堆在哪里?

What and where are the stack and heap?

编程语言书籍解释了值类型是在堆栈上创建的,引用类型是在堆上创建的,而没有解释这两个东西是什么。对此我还没有读到清楚的解释。我明白什么是堆栈。但是,

  • 它们在哪里和什么地方(物理上在真正的计算机内存中)?
  • 它们在多大程度上受操作系统或语言运行时控制?
  • 他们的范围是什么?
  • 它们的大小是由什么决定的?
  • 是什么让你跑得更快?


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

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

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

要直接回答您的问题:

To what extent are they controlled by the OS or language runtime?

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

What is their scope?

堆栈连接到线程,因此当线程退出时,堆栈将被回收。堆通常在应用程序启动时由运行时分配,并在应用程序(技术流程)退出时回收。

What determines the size of each of them?

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

What makes one faster?

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

清晰的演示:><br /><sub>image source:vikashazrati.wordpress.com<sub></P></p>
<div class=


堆栈:

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

堆:

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

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;


最重要的一点是堆和堆栈是内存分配方式的通用术语。它们可以以许多不同的方式实现,并且术语适用于基本概念。好的。

  • 在一堆项目中,项目按其放置的顺序一个接一个地放置在另一个项目上,您只能移除顶部的项目(而不将整个项目倾倒)。好的。

    Stack like a stack of papers好的。

    堆栈的简单性在于,您不需要维护包含已分配内存的每个部分的记录的表;您需要的唯一状态信息是指向堆栈末尾的单个指针。要分配和取消分配,只需递增和递减那个单指针。注意:有时可以实现一个堆栈,从内存段的顶部开始向下扩展,而不是向上增长。好的。

  • 在堆中,项目的放置方式没有特定的顺序。您可以按任何顺序访问和删除项目,因为没有明确的"top"项目。好的。

    Heap like a heap of licorice allsorts好的。

    堆分配要求维护一个完整的记录,记录分配的内存和没有分配的内存,以及一些开销维护,以减少碎片,找到足够大的连续内存段以适合请求的大小,等等。内存可以随时释放,留下可用空间。有时,内存分配器会执行维护任务,例如通过移动分配的内存来对内存进行碎片整理,或者垃圾收集—在运行时识别内存不再在作用域内并解除分配。好的。

这些图像应该能够很好地描述在堆栈和堆中分配和释放内存的两种方法。百胜!好的。

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

    如前所述,堆和堆栈是通用术语,可以通过多种方式实现。计算机程序通常有一个称为调用堆栈的堆栈,它存储与当前函数相关的信息,例如指向从哪个函数调用它的指针,以及任何局部变量。因为函数调用其他函数,然后返回,所以堆栈会增长和收缩,以将来自函数的信息保存在调用堆栈的下方。一个程序并没有真正的运行时控制权;它是由编程语言、操作系统甚至系统体系结构决定的。好的。

    堆是一个通用术语,用于动态和随机分配的任何内存,即无序分配。内存通常由操作系统分配,应用程序调用API函数来进行分配。管理动态分配的内存(通常由操作系统处理)需要一定的开销。好的。

  • 他们的范围是什么?好的。

    调用堆栈是一个低级概念,在编程的意义上它与"范围"没有关系。如果反汇编一些代码,您将看到对堆栈部分的相对指针样式引用,但是对于更高级别的语言而言,该语言强制使用自己的作用域规则。然而,堆栈的一个重要方面是,一旦函数返回,该函数的任何局部内容都将立即从堆栈中释放。考虑到您的编程语言是如何工作的,这是您期望的工作方式。在堆中,也很难定义。范围是操作系统公开的任何内容,但是您的编程语言可能会添加有关应用程序中的"范围"的规则。处理器体系结构和操作系统使用虚拟寻址,处理器将其转换为物理地址,并存在页面错误等。它们跟踪哪些页面属于哪些应用程序。不过,您不必担心这一点,因为您只需使用编程语言使用的任何方法来分配和释放内存,并检查错误(如果分配/释放由于任何原因失败)。好的。

  • 它们的大小是由什么决定的?好的。

    同样,它取决于语言、编译器、操作系统和体系结构。堆栈通常是预先分配的,因为根据定义,它必须是连续的内存(更多内容在最后一段中)。语言编译器或操作系统决定其大小。您不需要在堆栈上存储大量的数据,因此它足够大,不应该完全使用它,除非出现不需要的无休止递归(因此称为"堆栈溢出")或其他异常的编程决策。好的。

    堆是可以动态分配的任何内容的通用术语。根据你对它的看法,它的大小在不断变化。不管怎样,在现代处理器和操作系统中,它的确切工作方式是非常抽象的,因此通常您不需要太担心它是如何工作的,除非(在允许您使用的语言中)您不能使用尚未分配的内存或已释放的内存。好的。

  • 是什么让你跑得更快?好的。

    堆栈更快,因为所有可用内存总是连续的。不需要维护所有空闲内存段的列表,只需要一个指向当前堆栈顶部的指针。为此,编译器通常将此指针存储在一个特殊的快速寄存器中。更重要的是,栈上的后续操作通常集中在非常近的内存区域中,而在非常低的级别上,这对于死缓存上的处理器进行优化是很好的。好的。

好啊。


(我把这个答案从另一个或多或少是这个问题的翻版的问题中移走了。)好的。

您的问题的答案是特定于实现的,并且可能因编译器和处理器体系结构而异。然而,这里有一个简单的解释。好的。

  • 堆栈和堆都是从底层操作系统分配的内存区域(通常是按需映射到物理内存的虚拟内存)。
  • 在多线程环境中,每个线程都有自己的完全独立的堆栈,但它们将共享该堆。必须在堆上控制并发访问,而不能在堆栈上进行并发访问。

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

The heap好的。堆栈

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

The stack好的。

Can a function be allocated on the heap instead of a stack?

Ok.

不,函数(即局部变量或自动变量)的激活记录被分配到堆栈上,该堆栈不仅用于存储这些变量,还用于跟踪嵌套函数调用。好的。

如何管理堆实际上取决于运行时环境。C使用EDCOX1×0,C++使用EDCOX1,1,但是许多其他语言都有垃圾收集。好的。

然而,堆栈是一个更低级的特性,与处理器体系结构紧密相连。当空间不足时增加堆并不难,因为它可以在处理堆的库调用中实现。然而,增加堆栈通常是不可能的,因为只有当堆栈溢出太迟时才会发现;关闭执行线程是唯一可行的选择。好的。好啊。


在下面的C代码中

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

以下是如何管理内存的方法

Picture of variables on the stack

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

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

在爪哇,大多数对象直接进入堆。在C/C++等语言中,当您不处理指针时,结构和类通常可以保留在堆栈上。

更多信息可在这里找到:

堆栈和堆内存分配的区别«;timmurphy.org

这里:

在堆栈和堆上创建对象

本文是以上图片的来源:六个重要的.NET概念:堆栈、堆、值类型、引用类型、装箱和取消装箱-代码项目

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


堆栈当您调用一个函数时,该函数的参数加上一些其他开销就会放到堆栈上。一些信息(如返回时要去哪里)也存储在那里。当您在函数内部声明一个变量时,该变量也会在堆栈上分配。

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

机甲废场堆是一个通用名称,用于动态放置创建的数据。如果您不知道您的程序将要创建多少宇宙飞船,您可能会使用新的(或malloc或等效的)操作符来创建每个宇宙飞船。这种分配将持续一段时间,因此我们很可能会以不同于我们创建的顺序释放事物。

因此,堆要复杂得多,因为最终会有一些未使用的内存区域与块交错,这些块是内存碎片。找到所需大小的空闲内存是一个困难的问题。这就是应该避免使用堆的原因(尽管仍然经常使用堆)。

实施堆栈和堆的实现通常由运行时/OS来完成。通常情况下,性能至关重要的游戏和其他应用程序会创建自己的内存解决方案,从堆中获取大量内存,然后在内部分发,以避免依赖操作系统获取内存。

只有当你的内存使用与正常情况有很大的不同时,这才是可行的——也就是说,当你在一个巨大的操作中加载一个级别,然后在另一个巨大的操作中丢弃整个级别的游戏。

内存中的物理位置这一点没有你想象的那么重要,因为一种叫做虚拟内存的技术使你的程序认为你可以访问物理数据在其他地方(甚至硬盘上)的某个地址。。当调用树变深时,为堆栈获取的地址将按升序排列。堆的地址是不可预测的(即特定于实现),坦率地说,并不重要。


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

堆栈上没有分配"static"(即静态分配)变量。不要这样认为——很多人这样做只是因为"静态"听起来很像"堆栈"。它们实际上既不存在于堆栈中,也不存在于堆栈中。是所谓的数据段的一部分。

但是,通常最好考虑"范围"和"生存期",而不是"堆栈"和"堆"。

范围是指代码的哪些部分可以访问变量。一般来说,我们考虑本地作用域(只能由当前函数访问)和全局作用域(可以在任何地方访问),尽管作用域可能变得更加复杂。

生存期是指在程序执行期间分配和释放变量的时间。通常我们考虑静态分配(变量将在程序的整个持续时间内持续存在,使它对跨多个函数调用存储相同的信息非常有用)与自动分配(变量仅在对函数的单个调用期间持续存在,使其对存储仅在func期间使用的信息非常有用与动态分配(变量的持续时间是在运行时定义的,而不是像静态或自动这样的编译时)相比,操作和可以在完成后丢弃。

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

我将提供一些简单的带注释的C代码来说明所有这些。最好的学习方法是在调试器下运行程序并观察其行为。如果您喜欢阅读python,请跳到答案的末尾:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 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++中的一些语法选择加剧了这个问题,例如许多人认为全局变量不是静态的,因为下面的语法。

1
2
3
4
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"这个词,而是说一些类似"file"或"file limited"的范围。然而,许多人使用短语"static"或"static scope"来描述只能从一个代码文件访问的变量。在生存期的上下文中,"静态"始终意味着变量在程序启动时分配,在程序退出时释放。

有些人认为这些概念是C/C++特定的。它们不是。例如,下面的python示例说明了所有三种类型的分配(在解释语言中可能存在一些细微的差异,我在这里不讨论)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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


其他人的笔画回答得很好,所以我会介绍一些细节。

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

  • 在C中,您可以通过使用alloca获得可变长度分配的好处,alloca在堆栈上分配,而alloc在堆上分配。这个内存在您的RETURN语句中无法生存,但它对于草稿缓冲区很有用。

  • 在不经常使用的窗口上创建一个巨大的临时缓冲区并不是免费的。这是因为编译器将生成一个堆栈探测循环,每次输入函数时都会调用该循环以确保堆栈存在(因为Windows在堆栈末尾使用单个保护页来检测堆栈何时需要增长)。如果从堆栈末尾访问内存超过一页,则会崩溃)。例子:

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


    其他人已经直接回答了您的问题,但是在试图理解堆栈和堆时,我认为考虑传统的UNIX进程(没有线程和基于mmap()的分配器)的内存布局是很有帮助的。内存管理词汇表网页有一个此内存布局的图表。

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

    在没有虚拟内存的系统中,例如一些嵌入式系统,通常会应用相同的基本布局,但堆栈和堆的大小是固定的。然而,在其他嵌入式系统中(例如基于微芯片PIC微控制器的系统),程序栈是一个独立的内存块,不能由数据移动指令寻址,只能通过程序流指令(调用、返回等)进行修改或间接读取。其他架构(如IntelItanium处理器)具有多个堆栈。从这个意义上说,堆栈是CPU体系结构的一个元素。


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

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

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

    因为在启动程序之前已经分配了堆栈,所以在使用该堆栈之前不需要进行malloc,所以这是一个小优势。实际上,在拥有虚拟内存子系统的现代操作系统中,很难预测什么是快速的,什么是缓慢的,因为页面是如何实现的以及它们存储在哪里是一个实现细节。


    我想很多人在这个问题上给了你最正确的答案。

    然而,有一个遗漏的细节是,"堆"实际上应该称为"空闲存储"。这种区别的原因是原始的空闲存储是用一种称为"二项式堆"的数据结构实现的。因此,从malloc()/free()的早期实现中分配是从堆中分配的。然而,在今天,大多数自由存储都是用非常复杂的数据结构实现的,而不是二项式堆。


    什么是堆栈?

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

    Enter image description here

    Stacks in computing architectures are regions of memory where data is added or removed in a last-in-first-out manner.
    In a multi-threaded application, each thread will have its own stack.

    < /块引用>

    什么是堆?

    堆是杂乱无章地堆积起来的东西。

    Enter image description here

    In computing architectures the heap is an area of dynamically-allocated memory that is managed automatically by the operating system or the memory manager library.
    Memory on the heap is allocated, deallocated, and resized regularly during program execution, and this can lead to a problem called fragmentation.
    Fragmentation occurs when memory objects are allocated with small spaces in between that are too small to hold additional memory objects.
    The net result is a percentage of the heap space that is not usable for further memory allocations.

    < /块引用>

    两者合在一起

    In a multi-threaded application, each thread will have its own stack. But, all the different threads will share the heap.
    Because the different threads share the heap in a multi-threaded application, this also means that there has to be some coordination between the threads so that they don’t try to access and manipulate the same piece(s) of memory in the heap at the same time.

    < /块引用>

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

    The stack is much faster than the heap.
    This is because of the way that memory is allocated on the stack.
    Allocating memory on the stack is as simple as moving the stack pointer up.

    < /块引用>

    对于刚开始编程的人来说,使用堆栈可能是个好主意,因为它更简单。< BR>因为堆栈很小,所以当您确切地知道数据需要多少内存时,或者如果您知道数据的大小非常小,您就需要使用它。< BR>当你知道你的数据需要大量的内存,或者你只是不确定你需要多少内存(比如动态数组)时,最好使用堆。

    Java内存模型

    Enter image description here

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


    你可以用这个堆栈做一些有趣的事情。例如,您有类似alloca的函数(假设您可以绕过关于它使用的大量警告),这是malloc的一种形式,专门使用堆栈而不是堆作为内存。

    也就是说,基于堆栈的内存错误是我经历过的最糟糕的错误之一。如果使用堆内存,并且超出了所分配块的界限,则很有可能触发段错误。(不是100%:您的块可能与以前分配的块偶然相邻。)但是,由于在堆栈上创建的变量总是彼此相邻,因此越界写入可以更改另一个变量的值。我了解到,每当我觉得我的程序不再遵守逻辑法则时,它很可能是缓冲区溢出。


    简单地说,栈就是创建局部变量的地方。此外,每次调用子程序时,程序计数器(指向下一条机器指令的指针)和任何重要寄存器,有时参数会被推送到堆栈上。然后将子例程中的任何局部变量推送到堆栈上(并从堆栈中使用)。当子例程完成时,这些内容都会从堆栈中弹出。PC和寄存器数据得到并放回它原来的位置,因为它是弹出的,所以你的程序可以继续其愉快的方式。

    堆是内存的一个区域,动态内存分配是通过(显式的"new"或"allocate"调用)进行的。它是一种特殊的数据结构,可以跟踪不同大小的内存块及其分配状态。

    在"经典"系统中,RAM的布局是这样的:堆栈指针从内存底部开始,堆指针从顶部开始,并向彼此增长。如果它们重叠,则说明内存不足。不过,这不适用于现代的多线程操作系统。每个线程都必须有自己的堆栈,这些堆栈可以动态创建。


    来自wikianwser。

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

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

    在异常处理和线程执行中,堆栈非常重要。

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


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

    • 变量可以全局访问
    • 内存大小没有限制
    • (相对)访问速度较慢
    • 没有保证有效地利用空间,随着时间的推移,内存可能会变得支离破碎,因为内存块被分配,然后被释放。
    • 您必须管理内存(您负责分配和释放变量)
    • 可以使用realloc()调整变量大小

    好吧,简单地说,简而言之,它们是指有序的,而不是有序的…!

    叠加:在叠加项中,事物相互叠加,意味着处理速度更快、效率更高!…

    所以总是有一个索引来指向特定的项目,处理也会更快,项目之间也有关系!…

    堆:没有顺序,处理速度会变慢,值会混乱在一起,没有特定的顺序或索引…有随机性,它们之间没有关系…所以执行和使用时间可能会有所不同…

    我还创建了下面的图像来显示它们的外观:

    enter image description here


    简而言之

    堆栈用于静态内存分配,堆用于动态内存分配,都存储在计算机的RAM中。好的。详细地

    堆栈好的。

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

    使用堆栈存储变量的好处是,内存是为您管理的。你不必手动分配内存,也不必在不再需要时释放内存。更重要的是,由于CPU如此高效地组织堆栈内存,所以从堆栈变量读取和写入堆栈变量的速度非常快。好的。

    这里可以找到更多。好的。

    机甲废场好的。

    堆是计算机内存中的一个区域,它不是为您自动管理的,也不是由CPU严格管理的。它是一个更自由的浮动内存区域(并且更大)。要在堆上分配内存,必须使用malloc()或calloc(),这是内置的C函数。一旦您在堆上分配了内存,您将负责在不再需要该内存时使用free()来释放该内存。好的。

    如果您不这样做,您的程序将有所谓的内存泄漏。也就是说,堆上的内存仍将被放在一边(并且对其他进程不可用)。正如我们将在调试部分看到的,有一个叫做valgrind的工具可以帮助您检测内存泄漏。好的。

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

    与堆栈不同,在堆上创建的变量可以被程序中任何位置的任何函数访问。堆变量在作用域中本质上是全局的。好的。

    这里可以找到更多。好的。

    堆栈上分配的变量直接存储到内存中,对该内存的访问速度非常快,并且在编译程序时处理其分配。当一个函数或方法调用另一个函数,而该函数又调用另一个函数等时,所有这些函数的执行都将保持挂起状态,直到最后一个函数返回其值为止。堆栈总是按后进先出顺序保留,最近保留的块总是要释放的下一个块。这使得跟踪堆栈变得非常简单,从堆栈中释放块只需调整一个指针即可。好的。

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

    Enter image description here好的。

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

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

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

    Enter image description here好的。

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

    即使如此,这里和这里给出了更多的细节。好的。

    现在来看看你的问题答案。好的。

    To what extent are they controlled by the OS or language runtime?

    Ok.

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

    这里可以找到更多。好的。

    What is their scope?

    Ok.

    已经在顶部给出。好的。

    "You can use the stack if you know exactly how much data you need to allocate before compile time, and it is not too big. You can use the heap if you don't know exactly how much data you will need at runtime or if you need to allocate a lot of data."

    Ok.

    在这里可以找到更多。好的。

    What determines the size of each of them?

    Ok.

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

    What makes one faster?

    Ok.

    堆栈分配要快得多,因为它真正做的就是移动堆栈指针。使用内存池,您可以从堆分配中获得类似的性能,但这会增加一点复杂性和它自己的麻烦。好的。

    此外,stack与heap不仅要考虑性能,还可以告诉您很多关于对象预期寿命的信息。好的。

    从这里可以找到详细信息。好的。好啊。


    在20世纪80年代,Unix像兔子一样传播,大公司也在滚动。埃克森也有一个,几十个品牌的名字也被历史所遗忘。内存的布局由许多实现者自行决定。

    一个典型的C程序在内存中用通过更改brk()值增加的机会。通常,堆刚好低于此brk值而增加BRK会增加可用堆的数量。

    单个堆栈通常是堆下的一个区域,它是一个内存区。在下一个固定内存块的顶部之前不包含任何值。下一个块通常是可以被堆栈数据覆盖的代码。在它那个时代的著名黑客之一。

    一个典型的内存块是BSS(零值块)在一家制造商的产品中,它不小心被归零了。另一个是包含初始化值的数据,包括字符串和数字。第三种是包含crt(c runtime)、main、函数和库的代码。

    Unix中虚拟内存的出现改变了许多约束条件。这些块不需要连续的客观原因,或者是固定的大小,或者是现在订购的特定方式。当然,在Unix之前是不受这些约束的multics。这是一个展示那个时代记忆布局的示意图。

    A typical 1980s style UNIX C program memory layout


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

    stack, heap and static data


    几美分:我想,最好是用图形化的方式来绘制内存,而且更简单:

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

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

    简单的方法是:进程堆是进程和内部所有线程的通用方法,在常见情况下使用它来分配内存,比如malloc()。

    堆栈是用于存储在常见情况下函数返回指针和变量中的快速内存,在函数调用、局部函数变量中作为参数进行处理。


    因为有些答案是吹毛求疵的,我要贡献我的小钱。

    令人惊讶的是,没有人提到过多个(即与正在运行的操作系统级线程的数量无关)调用堆栈不仅可以在异国语言(PostScript)或平台(Intel Itanium)中找到,而且可以在光纤、绿色线程和一些协程实现中找到。

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

    在任何情况下,光纤、绿色线程和协同程序的目的都是在单个操作系统级线程内同时执行多个功能,但不并行执行(请参见本问题的区别),以有组织的方式将控制权来回转移。

    当使用光纤、绿色线程或协程时,每个函数通常有一个单独的堆栈。(从技术上讲,不只是一个堆栈,而是每个函数都有一个完整的执行上下文。最重要的是,CPU寄存器。)对于每个线程,堆栈的数量和并发运行的函数的数量一样多,线程根据程序的逻辑在执行每个函数之间切换。当一个函数运行到它的末尾时,它的堆栈就会被破坏。因此,堆栈的数量和生命周期是动态的,而不是由操作系统级线程的数量决定的!

    注意,我说过"每个函数通常有一个单独的堆栈"。Couroutine既有堆叠的实现,也有无堆叠的实现。最值得注意的StcFoost C++实现是Boost。Coroutine和微软PPL的EDOCX1 0。(然而,C++的可恢复函数(又名EDCOX1,1)和EDCOX1(2)",",这是对C++ 17提出的,很可能使用无栈协同程序。

    光纤到C++标准库的建议即将到来。还有一些第三方图书馆。绿色线程在Python和Ruby等语言中非常流行。


    我有一些东西要分享,尽管主要的要点已经被涵盖了。

    • 快速访问。
    • 存储在RAM中。
    • 函数调用与传递的局部变量和函数参数一起加载在这里。
    • 当程序超出范围时,空间会自动释放。
    • 存储在顺序存储器中。

    • 对堆栈的访问相对较慢。
    • 存储在RAM中。
    • 动态创建的变量存储在这里,稍后需要在使用后释放分配的内存。
    • 存储在内存分配完成的任何位置,始终由指针访问。

    有趣的注释:

    • 如果函数调用存储在堆中,则会导致两个混乱点:
    • 由于堆栈中的顺序存储,所以执行速度更快。堆中的存储会导致大量的时间消耗,从而使整个程序执行速度变慢。
    • 如果函数存储在堆中(指针指向的杂乱存储),就没有办法返回调用方地址(由于内存中的顺序存储,堆栈给出了这个地址)。


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

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

    您可以使用堆栈传递参数。即使它比使用寄存器慢(微处理器专家会说还是一本80年代的好的BIOS书…)

    • 没有堆栈,微处理器就不能工作。(我们无法想象没有子程序/函数的程序,即使是汇编语言)
    • 如果没有堆,它可以。(汇编语言程序可以在没有堆的情况下工作,因为堆是一个OS概念,而malloc是一个OS/lib调用。

    堆栈使用速度更快:

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


    真的!答案太多了,我觉得其中一个不正确…

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

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

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

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

    如果私有堆太大,它将与堆栈区域重叠;如果私有堆太大,堆栈也将与堆重叠。因为堆栈从一个更高的地址开始,一直向下一直到较低的地址,通过适当的黑客攻击,您可以使堆栈变得如此大,以至于它将超出私有堆区域并覆盖代码区域。然后,技巧是重叠足够多的代码区域,以便您能够钩住代码。这有点棘手,而且你可能会面临程序崩溃的风险,但这很容易,而且非常有效。

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

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

    堆栈由程序员控制,私有堆由操作系统管理,公共堆不受任何人控制,因为它是一个操作系统服务——您发出请求,请求被授予或拒绝。

    2b)范围是什么?

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

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

    堆栈和私有堆的大小由编译器运行时选项决定。公共堆在运行时使用大小参数初始化。

    2d)什么使一个更快?

    它们的设计不是为了快速,而是为了实用。程序员如何使用它们决定了它们是"快"还是"慢"

    裁判:

    https://norasandler.com/2019/02/18/write-a-编译器-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


    程序设计语言的存储分配策略

    C中的内存—堆栈、堆和静态

    enter image description hereMEMORY IN C – THE STACK, THE HEAP, AND STATIC

  • OP:操作系统或语言运行库在多大程度上控制它们?
  • 关于这个重要问题,我想补充几点:

    操作系统和公共语言运行时

    .NET框架的关键组件enter image description hereNET框架4.5体系结构enter image description here

    CLR的组件Components of CLRenter image description hereenter image description hereenter image description here