本节博客摘录于:http://blog.sina.com.cn/s/blog_4c2e13ff0101e3gr.html
通常我们都是学了标准c语言教程后从事单片机c语言的编写的, 那就先要明白一点,标准c语言实际上是起源于pc平台上的一种语言, 标准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 ,一个是returnaddress stack. 分别用于存放临时变量,局部变量,传递参数, 和函数返回地址.
这里需要注意的是, 局部变量存放在堆栈中的处理方式一定要保证堆栈足够大,特别是定义了局部数组变量的情况下,一旦数组过大,超过了堆栈大小就会发生堆栈溢出, 如果只是读取数据还好, 一旦写入数据,就会破坏堆栈空间以外的数据, 导致程序时常.
所以在单片机c编译器里写程序时一定要了解单片机编译器扩展c语言的不同之处, 绝不能死板地照搬标准c.
另外对于编译而言有这些规则:(摘录于:https://blog.csdn.net/chengdong1314/article/details/78663785)
r0-r3 用作传入函数参数,传出函数返回值。在子程序调用之间,可以将 r0-r3 用于任何用途。
被调用函数在返回之前不必恢复 r0-r3。如果调用函数需要再次使用 r0-r3 的内容,则它必须保留这些内容。
r4-r11 被用来存放函数的局部变量。如果被调用函数使用了这些寄存器,它在返回之前必须恢复这些寄存器的值。
r12 是内部调用暂时寄存器 ip。它在过程链接胶合代码(例如,交互操作胶合代码)中用于此角色。
在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复 r12。
13 是栈指针 sp。它不能用于任何其它用途。sp 中存放的值在退出被调用函数时必须与进入时的值相同。
r14 是链接寄存器 lr。如果您保存了返回地址,则可以在调用之间将 r14 用于其它用途,程序返回时要恢复
r15 是程序计数器 PC。它不能用于任何其它用途。
注意:在中断程序中,所有的寄存器都必须保护,编译器会自动保护R4~R11