• 什么是堆叠和堆叠?
  • 他们在电脑记忆中身处何方?
  • 它们在多大程度上受到操作系统或语言运行时间的控制?
  • 其范围是什么?
  • 是什么决定了它们的大小?
  • 是什么让一个更快?

当前回答

其他答案只是避免解释静态分配意味着什么。 因此,我将解释三种主要分配形式,以及它们通常与下面的堆积、堆叠和数据段的关系。 我还将在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.

区分寿命和范围之所以重要,一个特别令人印象深刻的例子说明为什么区分寿命和范围很重要,那就是变量可以具有本地范围,但有静态的寿命,例如,在上文的代码样本中“某些本地静态可变性 ” 。这些变量可以使我们共同但非正式的命名习惯非常混乱。例如,当我们说“某些本地可变性 ” 。当地当地" 我们通常是指 "本地覆盖范围自动分配变量" 当我们说全球时,我们通常指 "全球范围静态分配可变数" 不幸的是,当它谈到类似的事情"缩放的静态分配变量"很多人只是说..."对不对?".

C/C++中的一些语法选择加剧了这一问题,例如许多人认为全球变量并非“静态”,

int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

请注意, 将关键字“ 静态” 放在上面的声明中会防止 var2 具有全球范围。 然而, 全球 val1 具有静态分布。 这不是直观的。 因此, 我试图在描述范围时从不使用“ 静态” 一词, 而是说“ 文件” 或“ 文件有限” 的范围。 但是许多人使用“ 静态” 或“ 静态范围” 来描述一个只能从一个代码文件中访问的变量。 在生命周期中, “ 静态” 是指“ 静态” 或“ 文件有限” 的范围。 但是许多人使用“ 静态” 或“ 静态范围” 来描述一个只能从一个代码文件中访问的变量。始终始终表示变量在程序启动时分配,在程序退出时进行交易。

有些人认为这些概念是特定C/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

其他回答

堆叠 :

  • 存储在计算机内存中 就像堆积物一样
  • 堆叠上创建的变量将超出范围, 自动进行交易 。
  • 与堆积上的变量相比,分配速度要快得多。
  • 采用实际的堆叠数据结构。
  • 存储本地数据, 返回地址, 用于通过参数 。
  • 当堆叠使用过多时(大部分来自无限重现或过深重重循环,分配量很大), 堆叠就会溢出。
  • 在堆栈上创建的数据可以不用指针使用 。
  • 如果您确切知道在编译时间之前需要分配多少数据, 并且数据并不太大, 您就会使用堆叠 。
  • 通常在程序启动时已经确定了最大尺寸 。

热量 :

  • 存储在计算机内存 和堆叠一样。
  • 在 C+++ 中, 堆积上的变量必须手动销毁, 并且绝对不能脱离范围。 数据以delete, delete[],或free.
  • 相对于堆叠上的变量, 较慢分配速度 。
  • 用于按需分配一组数据供程序使用。
  • 当有大量拨款和交易时,就有可能支离破碎。
  • 在C++ 或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;

最重要的一点是,堆积和堆叠是记忆分配方法的通用术语,可以多种不同方式加以实施,这些术语适用于基本概念。

  • 在一个堆叠的项目中,项目坐在另一堆的上方,按其放置的顺序排列,你只能删除顶端的项目(不折叠整件事情)。

    Stack like a stack of papers

    堆叠的简单性在于您不需要保存包含分配内存每一部分的记录的表格; 您所需要的唯一状态信息是到堆栈尾端的单指针。 要分配和取消分配, 您只需递增和缩减该单指针。 注意: 有时可以安装堆叠, 以开始于内存的顶部, 向下延伸, 而不是向上增长 。

  • 在堆积中,项目放置方式没有特定顺序。您可以按任何顺序接触和删除项目,因为没有明确的“顶部”项目。

    Heap like a heap of licorice allsorts

    高空分配需要完整记录什么是记忆分配,什么是记忆分配,什么不是,以及一些间接维护,以减少碎裂,发现毗连的内存部分大到足以满足要求的大小,等等。 内存可以在离开空闲空间的任何时候进行分配。 有时,内存分配器将执行维护任务,例如通过移动分配的内存或垃圾收集来消除内存的分散性,或者在运行时识别内存不再在范围之内的运行时间并进行分配。

这些图像应该能很好地描述 在堆叠和堆肥中分配和释放记忆的两种方式。

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

    如前所述,堆叠和堆叠是一般术语,可以多种方式实施。呼叫堆叠存储与当前函数相关的信息, 如指向它从哪个函数调用, 以及任何本地变量。 因为函数调用其他函数, 然后返回, 堆叠会增长并缩放, 以便从调用堆栈往下更远的函数中保留信息。 一个程序实际上没有运行时间控制; 它由编程语言、 OS 甚至系统架构决定 。

    堆积是一个通用术语,用于动态和随机分配的任何内存;即失序。内存通常由操作系统分配,应用程序中调用 API 函数来分配。管理动态分配内存需要相当一部分管理费,通常由所用编程语言或环境的运行时间代码处理。

  • 其范围是什么?

    调用堆栈是一个低层次的概念, 以至于它与编程意义上的“ 范围” 无关。 如果您将一些代码拆解, 您将会看到与堆叠部分相对的指针样式引用, 但就更高层次的语言而言, 语言会强制实施它自己的范围规则 。 但是, 堆栈的一个重要方面是, 一旦一个函数返回, 任何本地的函数都会立即从堆叠中解开。 这与您所编程语言是如何工作的有关。 在堆放过程中, 它也很难定义。 范围是由操作系统所暴露的, 但是您的编程语言可能会增加它关于“ 范围” 在您的应用程序中是什么的规则 。 处理器结构和 OS 使用虚拟地址, 处理器可以翻译为物理地址, 并且有页面错误等 。 它们会跟踪哪些页面属于哪个应用程序。 但是, 您从不需要担心这一点, 因为您只是使用你编程语言用于分配和自由记忆的方法, 并检查错误( 如果由于任何原因分配/ 解析失败 ) 。

  • 是什么决定了每个孩子的大小?

    同样,它取决于语言、编译器、操作系统和架构。 堆叠通常是预先分配的, 因为根据定义它必须是连续的内存。 语言编译器或操作系统决定其大小。 您不会在堆叠中存储大量数据, 因此它会足够大, 永远不能被充分利用, 除非在无谓的循环( 例如“ 堆叠溢出 ” ) 或其他不寻常的编程决定下。

    对于任何可以动态分配的东西来说, 堆积是一个通用的术语。 取决于您看它的方式, 它的大小在不断变化。 在现代的处理器和操作系统中, 它的运作方式是非常抽象的, 所以通常你不需要担心它是如何在内心深处运作的, 除了( 在它允许你使用的语言中) 您不能使用你还没有分配到的记忆或者你已经释放的记忆。

  • 是什么让一个更快?

    堆叠速度更快, 因为所有自由内存总是毗连的 。 不需要保存自由内存所有部分的清单, 仅指堆叠当前顶部的单指针。 汇编者通常会将这个指针保存在特殊、 快速的文件中 。登记册登记簿更何况,堆叠上的后续操作通常集中在非常靠近的内存区内,这些内存区在非常低的水平上,对处理器置存的缓存器优化是有好处的。

OK,OK, OK, OK, OK, OK, OK, OK, OK,OK, OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,OK,简言简简短地说,它们的意思是:已订购未排序...!

堆堆叠:在堆叠的物品中,事情会相互占据顶端, 意味着处理的速度会更快,效率会更高!

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

堆肥:没有订单,处理速度会放慢, 价值会混乱在一起,没有具体的订单或索引... ......有随机的,它们之间没有关系... 所以执行和使用时间可以不同...

我还创造了下面的图像,以显示他们可能长得如何:

enter image description here

堆叠当您调用函数时,该函数的参数加上一些其他间接费用被放在堆栈中。有些信息(例如返回后将到何处)也存储在那里。当您在函数中声明变量时,该变量也分布在堆栈中。

拆分堆栈很简单, 因为您总是按照您分配的反向顺序进行排列。 在输入函数时添加堆叠材料, 当退出时相应数据将被删除。 这意味着您倾向于留在堆叠的小区域内, 除非您调用许多函数来调用其他函数( 或创建循环解决方案 ) 。

堆肥堆积是一个通用的名称, 用于您将创建的数据放在哪里 。 如果您不知道您的程序要创建多少宇宙飞船, 您可能会使用新的( 或商略或等效的) 操作员来创建每艘宇宙飞船 。 此分配将会停留一段时间, 因此我们很可能释放的东西, 与我们创建的顺序不同 。

因此,堆积要复杂得多,因为最终会出现一些未使用的内存区域,这些区域与块状的内存间断 — — 内存会变得支离破碎。 找到您需要的大小的自由内存是一个困难的问题。 这就是为什么应该避免堆积(尽管它仍然经常被使用 ) 。

执行 执行 执行堆叠和堆叠的操作通常要到运行时间/操作系统。 通常游戏和其他功能至关重要的应用程序会创造自己的内存解决方案,从堆叠中抓取大量内存,然后在内部将内存分离出来,以避免依赖操作系统进行内存。

只有当你的记忆用法与常规有很大不同时, 也就是在游戏中, 在一个巨大的操作中加载一个水平, 并且可以在另一个巨大的操作中将整个批量扔掉时, 这才是实际的。

内存物理位置这比你想的要少 因为一种技术叫做虚拟内存这使得您的程序认为您可以访问某位地址, 物理数据在其他地方( 甚至是硬盘上! ) 。 您获得的堆叠地址随着您的呼叫树越深, 顺序越大。 堆放的地址是不可预知的( 具体化) , 坦率地说并不重要 。

堆叠是记忆的一部分, 可以通过若干关键组装语言指令来操作, 如“ pop”( 移动并返回堆放中的值) 和“ push” (将值推到堆放中) , 但也可以调用( 调用子例程 - 将地址推回堆放中) 和调用( 从子例程返回 - 将堆放中的地址从堆放中跳出, 跳到堆放中) 。 这是堆叠指针登记册下的内存区域, 可根据需要设置 。 堆叠还用于将参数通过子例程, 并在调用子例程前保存登记册中的值 。

堆积是操作系统向一个应用程序提供的内存的一部分,通常通过像麦洛克这样的轮号。 在现代操作系统上,这个内存是一组只有呼叫程序才能进入的页面。

堆叠的大小在运行时确定, 通常在程序启动后不会增长。 在 C 程序中, 堆叠需要足够大, 以保持每个函数中所有声明的变量。 堆叠会根据需要动态增长, 但操作系统最终会发出呼唤( 它会增加的堆积量往往超过 merloc 所要求的值, 这样至少有些未来的中枢不需要返回内核以获取更多的内核内存。 这种行为通常可以自定义 )

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