最近花了点时间读了《深入理解C指针》这本书,读完这本书后,对于之前嵌入式C语言开发中很多一知半解的地方豁然开朗。对于之前学习以及工作中,很多没有注意的问题,也有了更加深刻的理解和认识。今天就花点时间整理下这段时间读完这本书后的所学所得,也方便后续查看。
我们在开发C语言程序的时候,程序需要在编译器中编译后,才能在对应产品中运行。在程序运行的过程中,内存的分配一般分为以下几个部分:
- 堆(heap)
- 栈(stack)
- BSS区
- 数据区
- 代码区
对于我们平时开发的C程序,加载到处理器的内存中运行,呈现出来的结构就大概分为以上五个区域。为了更好地理解,我们可以根据下面这张图去理解。
下面我们简单地对上述几个区域做一下介绍。首先从代码区开始。
代码区
我们需要了解的是,我们在使用C语言开发产品程序代码的时候,最终的开发好的程序是并不能直接在产品的处理器中直接运行的。而是需要经过编译器的编译,最终将我们的代码转换成CPU处理器所能够理解的机器指令,才能在CPU中正常运行。而代码区,我们可以简单地理解为我们编写好的程序(已经转换为机器指令的程序代码)存放的地方。CPU处理器会从这个地方取出机器指令去完成对应的操作,最终实现我们想让CPU所实现的功能(简单的算术运算,或者对应的硬件操作等)。
栈区
栈区,这个区域不需要我们去维护管理,在程序运行的过程中,会自动的分配和释放。可能有些人不太能理解这个区域是做什么用的,这里我们简单地做一下介绍。
我们在开发嵌入式C程序代码的过程中,经常会在定义一些函数,去完成特定的功能(例如:定义一个addfunc函数用于实现两个数的加法运算,或者说更加复杂的,定义一个函数来对一个链表进行查询操作),在这些函数中,我们不可避免地,会去定义一些局部变量(局部变量的概念就不在这里做介绍了,不理解的建议去过一遍谭浩强的C语言程序开发)。在程序运行的过程中,我们需要一个地方去存储我们定义的这些局部变量,调用函数时的参数值,返回值等,而这个地方就是栈。
栈区是处于不断变化的状态中,举个例子,当前,程序运行在main函数中,此时栈中存储的则是main函数中我们定义的一些局部变量。这个时候,假如我们在main函数中去调用其他函数,那么,栈中会自动进行出栈操作,然后再进行入栈操作,将我们调用的那个函数的相关的局部变量,函数参数,返回值等都压入到栈中。如下图所示:
最先开始,程序从main函数开始运行,在调用addfunc函数之前,栈中的存储内容可能是这样的。
而在调用了addfunc函数后,栈区的内容就会被自动地替换为addfunc函数中的局部变量等内容。在返回main函数后,又会自动进行出栈入栈操作。
我们在不断地调用函数的同时,也自动不断地进行入栈以及出栈的操作,也就是说,我们定义的局部变量,实际上是在内存上不是一直会存在的,可能会在某一次的入栈及出栈操作中被覆盖掉。举个例子,我们可以试着编译运行下面这段代码。
这里我们在main函数中定义了一个局部变量a和b,以及一个res变量。接着我们调用addfunc函数,先将a和b变量的值进行相加,然后存储到res变量中。最后输出结果。看到这里,如果对C语言有点基础的朋友,就知道这种做法是得不到我们想要的预期结果的。而这就是因为我们程序在运行过程中,栈区不断进行入栈出栈导致的。我们在main函数中定义了a,b和res后,调用addfunc函数,此时栈区会进行出栈以及入栈操作,在addfunc函数中,做完加法运算后,res变量的值存储在栈中某个地址中,但是在addfunc函数返回后,会被下一次出栈入栈操作给覆盖掉。因此我们得不到我们想要的结果。
堆区
上面介绍完栈区后,我们这里再简单说下堆区。其实堆区和栈区,指向的是同一片内存区域。不同的是,这两个区域在这一块内存区域的两头,随着程序的运行,根据需要,一个不断向下增长,一个不断向上增长。(如下图)这也是我们经常喜欢说“堆栈“,而不是将这两个地方单独说”栈区“或”堆区“的原因。
堆区不同于栈区的另一个地方,就是我们程序想要在堆上开辟内存使用,方法不同于栈。在C程序中,我们定义了一个局部变量并赋值,这个过程可以理解为我们在栈上开辟了一个空间来使用,而且我们不需要去理会这个空间后续的回收问题。但是在堆上开辟空间的方法则不同。我们可以调用malloc函数在堆上开辟我们所需要使用的内存空间。在最后这段空间使用完毕后,则需要我们再调用free函数来释放掉我们申请的这段空间。否则就会发生内存泄露问题。当内存泄露的次数多了,则可能出现堆栈溢出。简单的说,就是我们一直向系统索要内存空间来使用,却忘记了“归还“,系统内存空间是有限的,当系统内存空间不够程序运行使用时,则会导致堆栈溢出,程序再也无法正常运行。
BSS区
这个区域存放的是未初始化的全局变量和静态变量。
数据区
这个区域存放的是已初始化的全局变量和静态变量,以及常量等。如下图:
我们在main函数中,定义了a,b,c三个变量,a和b变量均保存去数据区中,而c是一个指针,是局部变量,存放的是“hello world”字符串的地址,而这个字符串则存在于数据区。如下图