首页 / 知识
关于内存管理:堆栈和堆在哪里?
2023-04-16 09:20:00
What and where are the stack and heap?编程语言书籍解释了值类型是在堆栈上创建的,引用类型是在堆上创建的,而没有解释这两个东西是什么。对此我还没有读到清楚的解释。我明白什么是堆栈。但是,
堆栈是为执行线程留出的临时空间的内存。调用函数时,在堆栈顶部为局部变量和一些簿记数据保留一个块。当该函数返回时,块将变为未使用状态,可以在下次调用函数时使用。堆栈总是按后进先出顺序保留;最近保留的块总是要释放的下一个块。这使得跟踪堆栈变得非常简单;从堆栈中释放块只需调整一个指针即可。 堆是为动态分配预留的内存。与堆栈不同,没有强制模式来分配和释放堆中的块;您可以随时分配一个块,并随时释放它。这使得跟踪在任何给定时间分配或释放堆的哪些部分变得更加复杂;有许多自定义堆分配器可用于调整不同使用模式的堆性能。 每个线程都有一个堆栈,而应用程序通常只有一个堆(尽管对于不同类型的分配有多个堆并不少见)。 要直接回答您的问题:
创建线程时,操作系统为每个系统级线程分配堆栈。通常,语言运行时调用操作系统来为应用程序分配堆。
堆栈连接到线程,因此当线程退出时,堆栈将被回收。堆通常在应用程序启动时由运行时分配,并在应用程序(技术流程)退出时回收。
创建线程时设置堆栈大小。堆的大小在应用程序启动时设置,但可以随着空间的需要而增长(分配器从操作系统请求更多内存)。
堆栈更快,因为访问模式使得从中分配和解除分配内存变得很简单(指针/整数只是简单地递增或递减),而堆在分配或解除分配中涉及更复杂的簿记。此外,堆栈中的每个字节往往被非常频繁地重用,这意味着它往往被映射到处理器的缓存,从而使其非常快速。堆的另一个性能问题是,堆主要是全局资源,通常必须是多线程安全的,即每个分配和释放都需要(通常)与程序中的"所有"其他堆访问同步。 清晰的演示:
最重要的一点是堆和堆栈是内存分配方式的通用术语。它们可以以许多不同的方式实现,并且术语适用于基本概念。好的。
这些图像应该能够很好地描述在堆栈和堆中分配和释放内存的两种方法。百胜!好的。
好啊。 (我把这个答案从另一个或多或少是这个问题的翻版的问题中移走了。)好的。 您的问题的答案是特定于实现的,并且可能因编译器和处理器体系结构而异。然而,这里有一个简单的解释。好的。
堆
不,函数(即局部变量或自动变量)的激活记录被分配到堆栈上,该堆栈不仅用于存储这些变量,还用于跟踪嵌套函数调用。好的。 如何管理堆实际上取决于运行时环境。C使用EDCOX1×0,C++使用EDCOX1,1,但是许多其他语言都有垃圾收集。好的。 然而,堆栈是一个更低级的特性,与处理器体系结构紧密相连。当空间不足时增加堆并不难,因为它可以在处理堆的库调用中实现。然而,增加堆栈通常是不可能的,因为只有当堆栈溢出太迟时才会发现;关闭执行线程是唯一可行的选择。好的。好啊。 在下面的C代码中
以下是如何管理内存的方法 对象(在我们更新它们时大小不同)会进入堆中,因为我们不知道它们在创建时会持续多长时间。在许多语言中,堆被垃圾收集以查找不再具有任何引用的对象(如cls1对象)。 在爪哇,大多数对象直接进入堆。在C/C++等语言中,当您不处理指针时,结构和类通常可以保留在堆栈上。 更多信息可在这里找到: 堆栈和堆内存分配的区别«;timmurphy.org 这里: 在堆栈和堆上创建对象 本文是以上图片的来源:六个重要的.NET概念:堆栈、堆、值类型、引用类型、装箱和取消装箱-代码项目 但请注意,它可能包含一些不准确之处。 堆栈当您调用一个函数时,该函数的参数加上一些其他开销就会放到堆栈上。一些信息(如返回时要去哪里)也存储在那里。当您在函数内部声明一个变量时,该变量也会在堆栈上分配。 解除堆栈的分配非常简单,因为您总是按与分配相反的顺序解除。当您输入函数时会添加堆栈内容,当您退出函数时会删除相应的数据。这意味着,除非调用许多调用其他函数(或创建递归解决方案)的函数,否则您倾向于停留在堆栈的一个小区域内。 机甲废场堆是一个通用名称,用于动态放置创建的数据。如果您不知道您的程序将要创建多少宇宙飞船,您可能会使用新的(或malloc或等效的)操作符来创建每个宇宙飞船。这种分配将持续一段时间,因此我们很可能会以不同于我们创建的顺序释放事物。 因此,堆要复杂得多,因为最终会有一些未使用的内存区域与块交错,这些块是内存碎片。找到所需大小的空闲内存是一个困难的问题。这就是应该避免使用堆的原因(尽管仍然经常使用堆)。 实施堆栈和堆的实现通常由运行时/OS来完成。通常情况下,性能至关重要的游戏和其他应用程序会创建自己的内存解决方案,从堆中获取大量内存,然后在内部分发,以避免依赖操作系统获取内存。 只有当你的内存使用与正常情况有很大的不同时,这才是可行的——也就是说,当你在一个巨大的操作中加载一个级别,然后在另一个巨大的操作中丢弃整个级别的游戏。 内存中的物理位置这一点没有你想象的那么重要,因为一种叫做虚拟内存的技术使你的程序认为你可以访问物理数据在其他地方(甚至硬盘上)的某个地址。。当调用树变深时,为堆栈获取的地址将按升序排列。堆的地址是不可预测的(即特定于实现),坦率地说,并不重要。 为了澄清,这个答案有错误的信息(托马斯在评论后修正了他的答案,酷:)。其他答案只是避免解释静态分配的含义。因此,我将解释三种主要的分配形式,以及它们通常如何与下面的堆、堆栈和数据段相关。我还将展示C/C++和Python中的一些例子来帮助人们理解。 堆栈上没有分配"static"(即静态分配)变量。不要这样认为——很多人这样做只是因为"静态"听起来很像"堆栈"。它们实际上既不存在于堆栈中,也不存在于堆栈中。是所谓的数据段的一部分。 但是,通常最好考虑"范围"和"生存期",而不是"堆栈"和"堆"。 范围是指代码的哪些部分可以访问变量。一般来说,我们考虑本地作用域(只能由当前函数访问)和全局作用域(可以在任何地方访问),尽管作用域可能变得更加复杂。 生存期是指在程序执行期间分配和释放变量的时间。通常我们考虑静态分配(变量将在程序的整个持续时间内持续存在,使它对跨多个函数调用存储相同的信息非常有用)与自动分配(变量仅在对函数的单个调用期间持续存在,使其对存储仅在func期间使用的信息非常有用与动态分配(变量的持续时间是在运行时定义的,而不是像静态或自动这样的编译时)相比,操作和可以在完成后丢弃。 尽管大多数编译器和解释器在使用堆栈、堆等方面都实现了类似的行为,但如果编译器想要正确的行为,有时可能会破坏这些约定。例如,由于优化,局部变量可能只存在于寄存器中,或者完全被删除,即使大多数局部变量存在于堆栈中。正如在一些注释中指出的,您可以自由地实现一个编译器,它甚至不使用堆栈或堆,而是使用其他一些存储机制(很少这样做,因为堆栈和堆非常适合这样做)。 我将提供一些简单的带注释的C代码来说明所有这些。最好的学习方法是在调试器下运行程序并观察其行为。如果您喜欢阅读python,请跳到答案的末尾:)
区分生存期和作用域之所以重要的一个特别令人痛心的例子是变量可以有局部作用域,但可以有静态生存期——例如,上面代码示例中的"somelocalstaticvariable"。这样的变量会使我们常见但非正式的命名习惯变得非常混乱。例如,当我们说"局部"时,我们通常是指"局部范围的自动分配变量",当我们说"全局"时,我们通常是指"全局范围的静态分配变量"。不幸的是,当涉及到"文件范围静态分配变量"之类的事情时,很多人只是说…呵呵???". C/C++中的一些语法选择加剧了这个问题,例如许多人认为全局变量不是静态的,因为下面的语法。
注意,在上面的声明中放入关键字"static"可以防止var2具有全局范围。然而,全局var1具有静态分配。这不是直觉!因此,在描述范围时,我尽量不要使用"static"这个词,而是说一些类似"file"或"file limited"的范围。然而,许多人使用短语"static"或"static scope"来描述只能从一个代码文件访问的变量。在生存期的上下文中,"静态"始终意味着变量在程序启动时分配,在程序退出时释放。 有些人认为这些概念是C/C++特定的。它们不是。例如,下面的python示例说明了所有三种类型的分配(在解释语言中可能存在一些细微的差异,我在这里不讨论)。
其他人的笔画回答得很好,所以我会介绍一些细节。 堆栈和堆不需要是单一的。一种常见的情况是,如果一个进程中有多个线程,那么就有多个堆栈。在这种情况下,每个线程都有自己的堆栈。您也可以有多个堆,例如,某些DLL配置可能导致不同堆分配不同的DLL,这就是为什么释放由不同库分配的内存通常是一个坏主意。 在C中,您可以通过使用alloca获得可变长度分配的好处,alloca在堆栈上分配,而alloc在堆上分配。这个内存在您的RETURN语句中无法生存,但它对于草稿缓冲区很有用。 在不经常使用的窗口上创建一个巨大的临时缓冲区并不是免费的。这是因为编译器将生成一个堆栈探测循环,每次输入函数时都会调用该循环以确保堆栈存在(因为Windows在堆栈末尾使用单个保护页来检测堆栈何时需要增长)。如果从堆栈末尾访问内存超过一页,则会崩溃)。例子:
其他人已经直接回答了您的问题,但是在试图理解堆栈和堆时,我认为考虑传统的UNIX进程(没有线程和基于 堆栈和堆通常位于进程虚拟地址空间的两端。堆栈在访问时自动增长,达到内核设置的大小(可以使用 在没有虚拟内存的系统中,例如一些嵌入式系统,通常会应用相同的基本布局,但堆栈和堆的大小是固定的。然而,在其他嵌入式系统中(例如基于微芯片PIC微控制器的系统),程序栈是一个独立的内存块,不能由数据移动指令寻址,只能通过程序流指令(调用、返回等)进行修改或间接读取。其他架构(如IntelItanium处理器)具有多个堆栈。从这个意义上说,堆栈是CPU体系结构的一个元素。 堆栈是内存的一部分,可以通过几个键汇编语言指令进行操作,例如"pop"(从堆栈中删除并返回值)和"push"(将值推送到堆栈),但也可以调用(调用子例程-推送地址以返回堆栈)和返回(从子例程返回-将地址弹出并跳到它上面)。它是堆栈指针寄存器下面的内存区域,可以根据需要进行设置。堆栈还用于向子例程传递参数,以及在调用子例程之前保留寄存器中的值。 堆是内存的一部分,由操作系统提供给应用程序,通常通过类似malloc的系统调用。在现代操作系统中,这个内存是一组只有调用进程才能访问的页面。 堆栈的大小在运行时确定,通常在程序启动后不会增长。在C程序中,堆栈必须足够大,以容纳每个函数中声明的每个变量。堆将根据需要动态增长,但操作系统最终正在进行调用(它通常会使堆增长超过malloc请求的值,这样至少将来的一些malloc将不需要返回内核以获得更多内存。这种行为通常是可定制的) 因为在启动程序之前已经分配了堆栈,所以在使用该堆栈之前不需要进行malloc,所以这是一个小优势。实际上,在拥有虚拟内存子系统的现代操作系统中,很难预测什么是快速的,什么是缓慢的,因为页面是如何实现的以及它们存储在哪里是一个实现细节。 我想很多人在这个问题上给了你最正确的答案。 然而,有一个遗漏的细节是,"堆"实际上应该称为"空闲存储"。这种区别的原因是原始的空闲存储是用一种称为"二项式堆"的数据结构实现的。因此,从malloc()/free()的早期实现中分配是从堆中分配的。然而,在今天,大多数自由存储都是用非常复杂的数据结构实现的,而不是二项式堆。 什么是堆栈? 堆栈是一堆对象,通常是整齐排列的对象。
|
最新内容
相关内容
linux如何打堆栈命令?
linux如何打堆栈命令?,系统,信息,电脑,代码,工作,公开,资料,数字,堆栈,命令,怎么在linux下俘获程序奔溃时的调用堆栈最简单的方法: 在内核中,prilinux解释命令解释符?
linux解释命令解释符?,系统,数据,名称,基础,工作,工具,状态,命令,脚本,进程,Linux中的sh命令的详细解释sh是linux中运行shell的命令,是shell的linuxls命令解释?
linuxls命令解释?,信息,系统,标准,命令,时间,名称,数据,文件,目录,观察,LS(LINUX中ls命令)详细资料大全ls命令是linux下最常用的命令之一,ls跟dlinux各种命令的解释?
linux各种命令的解释?,地址,工作,系统,信息,命令,目录,时间,管理,控制台,常用命令,linux的常用命令有哪些呢???希望带上解释date:打印或者设置linux保留堆栈命令?
linux保留堆栈命令?,地址,工作,系统,信息,管理,命令,目录,代码,名称,连续,linux系统常用操作命令linux常用命令有pwd命令、cd命令、ls命令、calinux路径命令解释?
linux路径命令解释?,系统,信息,设备,数据,工具,命令,文件,标准,发行,时间,linux查看路径命令1、linux命令如果记不得,可以使用man命令来查看某linux进程命令解释?
linux进程命令解释?,系统,状态,基础,进程,信息,时间,命令,实时,软件,名称,Linux中用于进程显示的top命令使用实例集锦1、Top 命令输出: 首先,让linux关机命令解释?
linux关机命令解释?,系统,工作,命令,时间,银河,信息,用户,级别,终端,指令,linux关机命令1、shutdown命令 shutdown命令用于安全关闭Linux系统linux内存管理命令?
linux内存管理命令?,系统,等级,工作,信息,命令,基础,地址,情况,管理,标准,Linux操作系统上常用的系统管理命令1、kill命令的工作原理是,向Linux什么是Python全局解释器锁(GIL)?
什么是Python全局解释器锁(GIL)?,数据,控制权,状态,持有,工具,时间,设计,培训,全局,线程,全局解释器锁是计算机程序设计语言解释器用于同步线Python解释器种类以及特点有哪些?
Python解释器种类以及特点有哪些?,代码,技术,培训,特点,字节,种类,速度,语言,方式,文件,当我们编写完Python代码时,我们会得到一个包含Python代Python解释器
Python解释器,代码,平台,网络,技术,官方网站,培训,提示符,字节,不同点,速度,当我们编写Python代码时,我们得到的是一个包含Python代码的以.py