CPU眼里的:堆和栈( 三 )


再写一个简单的函数heap,我们连续作4次的malloc操作 。每次只申请4字节的内存,并打印对应的内存地址:

CPU眼里的:堆和栈

文章插图
 
如你所见,指针变量p的值,也就是所得内存块的内存地址,在不断升高 。这也验证了那句话:“堆”的生长方向是由低端内存,向高端内存生长的 。
或许你也发现了,虽然每次调用malloc的代码,也是非常规律的,但每次得到的内存地址,也就是指针变量p的值,都是无规律变化的!这也暗示malloc的分配策略是比较复杂的,内存碎片或许正在悄悄产生 。
 
06
总结
1. “栈”内存由操作系统分配给每个任务(线程)私用,不可共享 。但由于“栈”往往得不到MMU的特殊保护,所以,这种愿望或许是难以实现的 。
因为只要得到某个栈变量的地址,线程A和线程B就可以相互攻击、黑化对方的“栈” 。而“堆”内存,往往可以被多个任务(线程)共享,所以,保证数据的完整性就显得非常必要 。
2. “栈”内存的空间一般比较小,多用于存放“栈”变量、返回地址等函数的栈帧信息 。但过深的函数调用、或者递归调用,会有“爆栈”(也叫:“堆栈”溢出、stack overflow)的风险 。一般随着函数的逐层调用,函数会自动的申请“栈”内存;随着函数的逐层返回,函数也会自动的回收“栈”内存 。一般情况下,不会产生内存碎片和内存泄露 。
而堆的内存空间相对比较大,可用于存放较大的数据 。堆内存的申请、释放,只能由程序员编写相应的代码,调用特定的函数,手动申请、释放 。但随着程序的复杂,内存碎片、内存泄露会时有发生 。
3. “栈”的访问效率极高,特别是申请、释放内存的操作,都被编译器高度优化 。往往只需要一条CPU指令(push、pop),改变一下CPU寄存器rsp的值,就能完成任务 。而堆的申请、释放函数,就会复杂许多,多次使用后,还会产生内存碎片 。
至于,在没有操作系统的时候,“堆”和“栈”,就需要程序员手动划分内存空间 。相信作过单片机开发的同学,对此一定不陌生 。
 
07
热点问题
Q1:malloc跟“堆”(heap)是什么关系?
A1:“堆”(heap)一般是操作系统附送给应用程序的一段内存块,在程序运行的时候,如果需要动态分配一些内存的话,我们往往通过函数malloc从“堆”里面申请内存,当然,使用完毕后我们往往会通过free函数,归还刚才申请的内存,从而保证“堆”这个内存块是充裕的 。当然,如果申请的内存量过大,超过了“堆”内存的默认空间大小时,操作系统往往也提供相应的系统调用,用以扩充“堆”内存的空间 。
 
Q2:使用malloc做动态内存分配,会产生碎片,分配速度也慢,那使用它有什么好处呢?
A2:相对静态内存分配,动态内存分配的内存使用效率比较高 。需要使用内存的时候就申请,使用完了,就释放、归还 。这样,即使一小块内存,也可能同时满足多个线程、任务对内存的需求 。
相反,像全局变量、静态变量,它们所占据的静态内存,从编译的时候就决定了它是永久归属的 。无论当前使用与否,它们都会占据这个内存,直到程序的生命周期结束 。
 
Q3:如果一个函数还没有运行,程序就被用户强制退出,会产生内存泄露吗?
A3:一般情况下,是不会产生内存泄露的 。因为程序运行时,所占用的所有内存资源(包括:“堆”、“栈”、数据段、代码段),操作系统都有相应的记录 。在操作系统得到用户强制退出的命令时,操作系统往往会先释放、回收该程序占据的所有内存、文件等资源 。
 
Q4:“堆排序”,跟这里说的“堆”,是什么关系?
A4:没有任何关系 。“堆排序”是对一种排序算法的形象描述 。我们的这里说的“堆”是指:一般由操作系统提供给程序运行的内存块 。

【CPU眼里的:堆和栈】


推荐阅读