分类: 嵌入式
2013-11-12 11:49:46
堆和栈
堆(heap)和栈(stack)是非常重要的概念,当我们进行程序开发时理解它们非常重要,尤其是对于嵌入式系统开发。比如在嵌入式系统中,任务的栈通常都很小,可能也就几K字节。在这种情况下,我们就应当尽可能不要将占用内存大的变量分配在栈上,而是应当分配在堆上;此外,也尽量不要采用递归的方式来设计程序,否则很容易造成栈溢出。 图 1 一旦boot loader运行了我们的单体程序,我们说boot loader就不存在了,那此时boot loader所占用的内存空间也就释放出来了,如图 2所示。从图中可以看出内存中的闲置空间加大了。那闲置空间被我们的单体程序用来做什么呢?做堆!在单体程序中的操作系统部分,会提供一定的管理模块来管理这块堆,并提供API(Application Programming Interface,应用程序编程接口)让我们调用,从而实现从堆中分配或是释放内存,这些API类似于C语言中的malloc ()/free ()。堆在管理上有一个特点,从堆中分配出来的内存应当是以某一大小字节为边界的。比如,如果CPU中的double类型是占用内存最多的数据类型且是8字节,那么堆分配出来的内存就必须保证是以8字节为边界的。这一点请读者想一想为什么?除了采用动态的内存分配,在嵌入式系统中通常还会采用固定大小内存块的分配方法,这种分配方法的好处是非常的快,而且这种内存在使用的过程中不会产生内存碎片。 图 2 堆我们说过了,那接下来我们看一看如果我们的单体程序继续运行,会出现什么样的内存布局。我们知道,通常我们的单体程序在初始化时往往需要创建多个任务来实现其应用功能。对于每一个任务,它一块内存是私有的,那就是栈!当任务运行时,其需要用栈来做为函数调用时的参数传递空间,以及用栈来存储函数内的局部变量。假设我们的单体程序需要创建两个任务A和B,这需要通过调用操作系统中的任务创建函数来达到这一目的。操作系统所提供的任务创建API往往需要我们指定任务栈的大小,有的甚至可以指定栈内存空间。一旦任务创建的API被调用,那么操作系统会调用堆分配API为任务分配栈,此时的内存布局如图 3所示。任务创建完了以后,各任务就可以根据应用程序逻辑的需要审请堆空间以实现其业务逻辑。 图 3 对于堆我们已经知道了必须调用相应的API来分配内存,那从栈空间分配内存也需要调用API吗?答案是通常不需要,为什么是通常?因为,在有的平台上(Linux上就是)提供栈空间的分配API,即这种API被调用时,是从调用任务的栈空间中分配内存的。对于这一功能,在嵌入式系统中使用得非常的少,我也不建议大家使用。对于下面的代码,mem_main、mem_foo和mem_bar的大小是4K字节(假设int类型的大小是4字节),这些内存就是自动(注意是自动)分配在运行任务的栈上的。我们假设某个任务当前所使用的栈是零字节,当这一任务运行到main中且没有进入foo ()时,其所占用的空间大小是大约4K字节,之所以用大约这个词,是因为函数的调用还有其它的栈开销。一旦任务运行进入foo ()函数但没有进入bar ()函数,那么所占用的栈的大小就变为大约8K字节。同样的,如果程序运行进入bar ()函数,那么所占用的栈空间大约就是12K字节了。
1)函数的调用深度越是深,由于每一级的函数通常都会有局部变量,那么所使用的栈空间也会累积得越大。 2)递归调用需要的栈空间会相对的大(视具体的情况),在嵌入式系统中也建议少用。 3)我们应当尽可能的不要在函数中定义占用内存空间较大的局部变量。 下面,我们总结一下堆与栈的区别,它们是: 1)堆是大家共享的。任务可以通过调用API来从堆中分配内存空间。 2)栈是任务所独有的。在嵌入式系统中,当一个任务创建起来后其栈空间的大小往往是定了的。函数中的局部变量是由编程语言自动从栈上分配的,我们不需要调用API进行空间分配。 最后我有一个问题留给读者您,这个问题是: 前面的讲解中,我们说任务的栈是由操作系统的任务创建API从堆中分配出来的,那栈是否也可以位于.data段或是.bss段中呢?为什么? 答案 由于堆从本质上说来就是一块内存,由于在C语言中一块内存可以从堆中分配,也可以从.data段或是.bss段中分配。因此,任务的栈也是可以从这三块内存中分配获得,也就是说最终的答案是:可以。 |
原文地址:http://yunli.blog.51cto.com/831344/186896 |