单片机编译器中局部变量和全局变量的深入解析

2019-04-15 17:06发布

通常我们都是学了标准c语言教程后从事单片机c语言的编写的, 那就先要明白一点, 标准c语言实际上是起源于pc平台上的一种语言, 标准c语言肯定是不会照顾到单片机的特殊性的. 因此单片机c编译器中的c语言是一种基于标准c,但是又有相应修改扩充的扩展c语言.  所以在单片机c编译器里写程序时一定要了解单片机编译器扩展c语言的不同之处, 绝不能死板地照搬标准c.       在标准c里, 局部变量是函数在调用的时候才临时分配存储空间的,全局变量是程序整个生命周期都一直存在的.  不过要知道,临时分配存储空间是需要操作系统内存管理程序支持的, 单片机中通常都没有操作系统,也就不能实现像pc平台中那样的局部变量的空间分配.  这里就需要深入了解一下单片机的c编译器究竟是如何处理局部变量的,如果对此没有概念,碰到调试过程中的一些奇异现象恐怕只能觉匪夷所思了.       另外需要知道的一点是, 不同的编译器对于局部变量的处理方法也不一样, 不能学了一个就到处照搬. 这里拿KEIL C ,IARAVR, ICCAVR这个三个编译器做分析比较.                首先说 Keil C51 , 它的局部变量并不是在堆栈中, C51 为了提高代码的效率, 根据 51 处理器的特性. 编译器对函数局部变量的安排进行了处理.局部变量如果不能分配到 寄存器里, 就放在 RAM 中了.编译器通过覆盖分析, 可以共享局部变量的地址空间.。 最终的DATA使用量取决于调用链中那个使用DATA最多的链。所以,在程序中增加一个局部变量,如果不是位于那个使用DATA最多的链中,需要的DATA数量是有可能不会增加的。

      如:main()->f11()->f12()->f13().... // 链1
              |----->f21()->f22()->f23().... // 链2

      因为f11(),f21()不在同一个调用链上,显然,f11()中使用的局部变量,可以和f21()中的局部变量,使用同一个存储单元。因为它们中的任何一个处在生命期内的话,另一个必然已经离开它的生命周期,同时它的局部变量也离开了它的生命周期,这些局部变量所占用的存储单元当然可以另做它用了。
      假设链1目前的局部变量需要50个存储单元,链2需要40个存储单元。那么你在链2中加入不多于10个单元的局部变量的话,程序最终需要的存储单元数量是不会增加的。            再说ICCAVR ,  它把局部变量存放在软件堆栈空间中.  ICCAVR使用两个堆栈:一个用于子程序调用和中断操作的硬件堆栈,一个用于传递参数、临时变量和局部变量的软件堆栈。硬件堆栈是从数据内存的顶部开始分配的,在硬件堆栈下面再分配一定数量的字节作为软件堆栈。        IARAVR对于局部变量的处理方法与ICCAVR一样. 它也有两个堆栈,一个是data stack ,一个是return address stack.  分别用于存放临时变量,局部变量,传递参数, 和函数返回地址.        这里需要注意的是, 局部变量存放在堆栈中的处理方式一定要保证堆栈足够大, 特别是定义了局部数组变量的情况下,一旦数组过大,超过了堆栈大小就会发生堆栈溢出,  如果只是读取数据还好, 一旦写入数据,就会破坏堆栈空间以外的数据, 导致程序时常.