我只是做了一个汇总。转载请注明出处。
不时更新中。
内容比较杂,如果下面的内容有什么问题,请告诉我。谢谢!
参考资料:
http://blog.csdn.net/zhbsniper/article/details/7176136
http://blog.csdn.net/husion01/article/details/8443289
Keil C 使用经过扩展的 ANSI C 作为编写语言,不支持结构体内的函数和函数内联,函数体内所有的变量定义都应放在其它代码的前面,且 for 循环初始条件中作为计数器的变量是不能像C++那样即用即定义的。
1.51单片机的存储器配置为:
1.1.内存 256B,地址为0x00 – 0xFF。其中
1.1.1.低128B(data/idata)可直接/间接寻址,包括寄存器也使用这部分的内存。
1.1.2.高128B(idata)只可间接寻址(Intel 8052)。
1.2.特殊功能寄存器(sfr),128B,其内存地址使用的是 0x80 – 0xFF,只可直接寻址。也就是说,SFR与内存的高128B的地址是一样的,但存储并不是重叠的。它们依靠不同的寻址方式来区分。
1.3.外存(xdata),64KB,地址为 0x0000 – 0xFFFF。使用16位的 DPTR 间接寻址。
1.4.代码区(code),64KB,地址为 0x0000 – 0xFFFF。
因此,Keil C 中的指针有三种,分别对应不同的存储区域:
char xdata *px; /* ptr to xdata */ char idata *pi; /* ptr to idata */ char code *pc; /* ptr to code */
2.可以使用 bit 数据类型来定义布尔型变量。但无法定义 bit 数组或指针。
3.可以使用 #define 指令来定义简单的数值常量,这样可以保证程序效率最高。如果使用 const 修饰符定义常量数组或结构体,则在编译时会根据变量前的其他修饰符(如 idata、xdata、pdata)占用相应的内存区域,这是十分浪费的。一般定义常量的方法是将常量放在程序段。例如
code const char data[16] = {1, 2, 3, ...};
由于程序段一般是只读的,因此更加常用的声明方法是
code char data[16] = {1, 2, 3, ...};
在默认情况下,Keil会将程序中使用到的字符串常量放在程序段。
4.Keil对中文的支持不是很好,这主要体现在定义含有中文的字符串常量的时候,在编译后的使用过程中可能会发现实际生成的字符串不正确。解决方法是使用其他工具,例如 WinHex 将字符串转换为字节数组。例如
//请输入一个数字 code char string1[] = {0xC7, 0xEB, 0xCA, 0xE4, 0xC8, 0xEB, 0xD2, 0xBB, 0xB8, 0xF6, 0xCA, 0xFD, 0xD7, 0xD6, 0xA3, 0xBA, 0x00}; ... puts(string1); //将字符串送入 UART
注意ASCIIZ字符串应当以 0x00 结尾。
5.Keil中函数的前三个参数是通过寄存器来传递的,其它参数通过内存中的固定地址传递。
6.Keil的链接器BL51使用了数据覆盖(Data Overlaying)技术,而不是堆栈来保存函数的局部变量。在链接的时候,BL51会生成函数的调用树。如果两个函数之间没有调用关系,那么这两个函数的某些局部变量可能会使用相同的内存地址。
BL51这样做的原因并不是为了节约内存。事实上,这样做根本节约不了内存。
6.>如果一个函数func1调用另一个函数func2,那么func1、func2的局部变量根本就不能是同一块内存。C51还是要为他们分配不同的RAM,这跟使用堆栈相比,节约不了内存。
6.>如果func1,func2不是在一个调用链上,那么C51可以通过覆盖分析,让它们的局部变量共享相同的内存地址。但这样也不会比使用堆栈节约内存,因为既然它们是在不同的调用链上,那么当其中一个函数运行时,那么另外一个函数必然不在其生命期内,它所占用的堆栈也已释放,归还给系统。
真实的原因(C51使用覆盖段作为局部变量的存放地的原因)是51的指令系统没有一个有效的相对寻址(变址寻址)的指令,这使得使用堆栈作为变量的代价太过昂贵。
然而,BL51的数据覆盖会带来以下两个问题:
6.1.默认情况下,所有的函数是不可重入的。而递归函数应当使用 reentrant 关键字:
int fact(int n) reentrant { if (n) return n * fact(n - 1); return 1; }
这样BL51会在内存中开辟出一段区域用于模拟堆栈。
6.2.BL51在生成调用树的时候,对函数之间直接调用的分析是正确的,但对含有指针的间接调用则不能很好地处理。例如
int (*Action)(); int MyAction() { return 100; } int CallAction() { return Action(); } void main() { Action = MyAction; CallAction(); }
如果工程名为 Test,源代码文件为 test.c,那么在编译后,我们可以在 Test.M51 文件中找到
SEGMENT +--> CALLED SEGMENT --------------------- ?C_C51STARTUP +--> ?PR?MAIN?TEST ?PR?MAIN?TEST +--> ?PR?MYACTION?TEST +--> ?PR?CALLACTION?TEST
也就是说,BL51认为main函数调用了CallAction和MyAction。但其实main只是取得了MyAction的地址。实际的调用发生在CallAction函数中。
错误的调用树可能会带来不必要的内存浪费和 WARNING L13: RECURSIVE CALL TO SEGMENT。
为了修正调用树,我们可以在 Options for Target 对话框的 BL51 Misc 选项卡下的 Overlay 中填入
main ~ MyAction, /*删除错误的调用关系*/ CallAction ! MyAction /*建立正确的调用关系*/
随后,我们便会发现调用树变成了
?C_C51STARTUP +--> ?PR?MAIN?TEST ?PR?MAIN?TEST +--> ?PR?CALLACTION?TEST ?PR?CALLACTION?TEST +--> ?PR?MYACTION?TEST
修正完毕。
也可以打开“use linker control file”,然后依次点击 Create 和 Edit,并在 .lin 文件中加入
OVERLAY ( main ~ MyAction, /*删除错误的调用关系*/ CallAction ! MyAction /*建立正确的调用关系*/ )
也可以达到相同的目的。
在尝试使用静态函数指针来保存函数的地址以备随后的调用时,可能会发生 WARNING L13。此时建议对调用树进行修正。具体的实例可以参阅 BL51 User’s Guide/Data Overlaying/Manipulating the Call Tree/Function Pointers一节。
关于修改调用树的具体信息,请参阅 BL51 User’s Guide/Linking Programs/Directives/OVERLAY 一节。
7.STARTUP.A51是每个工程必须具有的引导文件,其中完成了变量的初始化操作。如果你在建立工程时没有将此文件加入,那么Keil会使用默认的STARTUP.A51完成编译。
8.使用总线结构时,可以使用 xdata 的指针(xdata*),absacc.h中的部分宏,或者是在声明变量时使用 _at_ 关键字来访问指定地址的外设IO。
对于使用 _at_ 关键字的情况,应当使用下面的方式来定义外设变量
xdata volatile char LcdIO _at_ 0x8000;
其中,volatile 是为了防止 Keil 对变量存取过度优化,例如防止将下面这两行代码的第一行优化掉:
LcdIO = 0x00; LcdIO = 0x05;
9.不要在中断处理程序中使用printf。