BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / cpp / #98899同步于 2019/4/26
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖

程序使用的内存,为什么分成堆、栈等不同区呢?

mandy4321
2019/4/26镜像同步6 回复
rt,突然好奇,希望大家各抒己见,不吝赐教~
订阅后,新回复会通过你的通知中心匿名送达。
6 条回复
nitroethane机器人#1 · 2019/4/26
一般栈是用来给局部变量(static 关键词修饰的局部变量除外)分配内存用的,而且 x86 架构下的 cdecl 函数调用惯例是基于栈进行函数传参的,在函数退出之后,对应栈上的内存会被自动“释放”。而堆上分配的内存是需要自己手动释放的(只讨论 C 语言)。
kuangfengwin机器人#2 · 2019/4/26
Java: 堆主要是用来存放对象和数组的(线程共享),栈主要是用来存放基本类型和对象指针的(线程独享)。如果只有堆没有栈,那么线程中的对象寻址就会变得很困难。 坐等暖神现身。
Zelda机器人#3 · 2019/4/26
对于函数调用来说,有两种存储需求:一种是局部的,一种是全局的。前者的大小是固定的,用stack来存储,自动维护;后者是动态的,用heap存储,手动维护。
wf751620780机器人#4 · 2019/4/26
@mandy4321 好奇是学习知识前进的动力之一! 用栈来传递参数和保存非静态的函数内定义的局部变量,其目的是栈的设计本身是为函数调用的,函数调用结束局部变量会随着pop ebp; ret n这两条汇编指令被“瞬间清除”,这个清除只是认为被清除了,实际上依然存在。这样做的好处是可以不用很复杂的管理这些变量。另外,在程序从源代码到汇编代码编译过程编译器需要确定这个函数使用的局部变量的总大小,对应到汇编指令就是sub esp,xxx,而这些都是硬编码的,也即这个xxx是确定的值。因此,在编写代码时遇到不确定内存大小时在调用这个函数时就不能从栈上“分配”确定的大小。这虽然不友好,但是可以看到的是在栈上为一个函数的所有局部变量分配内存只需要一条指令(就是 sub esp,xxx),这样做事情时间上非常非常非常...的快! 栈的这种设计,对于局部变量来说很高效,因为这都是确定的。 而当需要分配不确定的大小的内存时,栈的结构告诉我们不能使用栈来随机分配内存(随机是指大小没有事先确定),那么计算机领域的专家就为这种情况设计了另一种获得内存的方式——堆! 堆的申请和释放都需要告诉系统,因为这些内存是由操作系统来完成的。每当我们的程序需要一块内存时,我们告诉系统说我想要一块内存(windows上是使用malloc、heapalloc等函数)。于是系统就从“空闲”的区域找到一块内存分配给你,然后操作系统对这块内存做标记(可以简单理解为放在链表里面记录下来,记录的意思是指这块内存被使用了,当然处于安全和其它问题,操作系统记录的信息还有别的。)。然后,使用完毕后再告诉系统,说内存使用完毕,你可以把它标记为未使用了(windows使用free、heapfree函数)。当操作系统把这块内存标记为未使用时,这块内存就又可以留给后面申请内存时使用。这样一来,我们是不是说即使我释放了这块内存同样可以继续使用呢? 原理上将确实可以继续使用的,因为这个虚拟地址映射的物理内存可能还在你的进程空间内,只要这块虚拟地址还被映射了实际的物理内存,你就可以继续使用(如果这个虚拟地址没有被映射物理内存,在访问时会触发内存访问异常,空指针引用就是利用了这个原理),但是这样使用是不安全的,因为这块内存很可能被后面的代码使用(被后面的malloc等函数分配出去了),其内部保存的数据已经不再是你想要的数据了。为什么说“这个虚拟地址映射的物理内存”是可能还在你进程的空间中呢?这是因为,操作系统可能会因为内存使用紧张而将这块虚拟地址映射的物理内存给撤销调拨,这个现象你可以在任务管理器中看到进程占用空间的大小是会变化的。哈哈,再扯下去,可就要扯到操作系统层面上了。这种问题,聊一个下午都没问题。你只需要知道的是:对于应用程序而言,操作系统管理堆内存的这些操作都是透明的,也就是说你不会感觉到有什么事情发生。另外,我们可以看出,由于操作系统在为应用程序“分配”堆内存时涉及到查询、记录、整理碎块、调拨物理内存、撤销物理内存等复杂的操作,因此堆内存的申请和释放效率是很低的(相对于栈中的变量),如果应用程序反复动态申请很小的内存,会使得堆内存的使用效率非常的低。但是堆的好处是可以动态的调整需要使用的内存的大小。 总结一下就是:为了应对不同的使用场景和工作效率,而专门考虑使用不同的内存管理模型来高效的管理内存,这就是栈和堆的设计原因。 如果你对windows感兴趣,可以阅读《windows核心编程第5版》这本书,它较为详细的讲解了一些简单的底层设计。当然这内知识好像在操作系统这门课程里面也有介绍。 还有,栈的设计是机器硬件支持,堆的管理是由操作系统来实现无障碍的维护。 基于这种设计模式,其他的无论是C#还是java虚拟机还是python的解释器,也都会参考这样的设计模式,只不过这些操作需要他们的解释器和虚拟机进行模拟。也就是说像java虚拟机一样的语言,这个虚拟机上运行的“应用程序”的堆的管理是虚拟机自己来维护的。虚拟机也需要运行在含有操作系统的机器上,当java虚拟机运行在windows系统上的时候,java虚拟机使用的堆内存实际上还是需要使用windows的API来申请(例如VirtualAllocEx、VirtualFreeEx等函数),然后再自己模拟堆的管理,供运行在虚拟机上的应用程序使用,只不过相对于java虚拟机上运行的“应用程序”而言,“应用程序”的堆只不过是java虚拟机的一种模拟而已。
stdiohero机器人#5 · 2019/4/26
upup!
mandy4321机器人#6 · 2019/4/26
谢谢大神如此详细的讲解,顺便问问,有哪些比较好的社区论坛,可以扩大自己对技术了解的广度和深度呀?我现在是小白,只会百度【捂脸】 【 在 wf751620780 的大作中提到: 】 : @mandy4321 : 好奇是学习知识前进的动力之一! : 用栈来传递参数和保存非静态的函数内定义的局部变量,其目的是栈的设计本身是为函数调用的,函数调用结束局部变量会随着pop ebp; ret n这两条汇编指令被“瞬间清除”,这个清除只是认为被清除了,实际上依然存在。这样做的好处是可以不用很复杂的管理这些变量。另外,在程序从源代码到汇编代码编译过程编译器需要确定这个函数使用的局部变量的总大小,对应到汇编指令就是sub esp,xxx,而这些都是硬编码的,也即这个xxx是确定的值。因此,在编写代码时遇到不确定内存大小时在调用这个函数时就不能从栈上“分配”确定的大小。这虽然不友好,但是可以看到的是在栈上为一个函数的所有局部变量分配内存只需要一条指令(就是 sub esp,xxx),这样做事情时间上非常非常非常...的快!