好多人说编译器只是工具,重要的在于算法和思想。
这话说的本来没错,但要有一个条件在先:那就是你真正掌握了你所用的编译器。但,真正熟悉编译器的却并不多见。当你深入了解一个编译器后,你能像用汇编一样用C,可以像汇编那样随心所欲的操作MCU!
了解一个编译器,首先应该有汇编的基础,不要求能用汇编编写程序或做过项目,但至少看的懂!不熟悉汇编的嵌入式程序员是不合格的程序员!
了解一个编译器,最好的方法是看它自带的帮助文件,至少要看过Compiler User's Guide ,至少遇到问题会想到到帮助中查找方法,虽然帮助大多是E文。
工作以来一直使用keil MDK编译器,对于这个编译器的界面以及设置大家可以在网上搜一下就找到了,今天我们主要来看一看keil MDK编译器的一些细节,看看这些细节,你知道多少。
1. 在所有的内部和外部标识符中,大写和小写字符不同。
2. 默认情况下,char 类型的数据项是无符号的。它们可以显式地声明为signed char 或 unsigned char。
3.基本数据类型的大小和对齐:
注:
a. 通常局部变量保留在寄存器中,但当局部变量太多放到栈里的时候,它们总是字对齐的。例如局部char变量在栈里以4为边界对齐;
b. 压缩类型的自然对齐方式为1。使用关键字__packed来压缩特定结构,将所有有效类型的对齐边界设置为1.
4. 整数以二进制补码形式表示;浮点量按IEEE格式存储。
5. 有符号量的右移是算术移位,即移位时要保证符号位不改变。
6. 对于int类的值:超过31位的左移结果为零;无符号值或正的有符号值超过31位的右移结果为零。负的有符号值移位结果为-1。
7. 整数除法的余数的符号于被除数相同,由ISO C90标准得出;
8. 如果整型值被截断为短的有符号整型,则通过放弃适当数目的最高有效位来得到结果。如果原始数是太大的正或负数,对于新的类型 ,无法保证结果的符号将于原始数相同。所以强制类型转化的时候,对转换的结果一定要清晰。
9. 整型数超界不引发异常;像unsigned char test; test=1000;这类是不会报错的,赋值或计算时务必小心。
10. 默认情况下,整型数除以零返回零。
11. 对于两个指向相同类型和对齐属性的指针相减,计算结果如下表达式所示:
((int)a (int)b) / (int)sizeof(指向数据的类型)
12. 在严格C中,枚举值必须被表示为整型,例如,必须在2147483648 到+2147483647的范围内。但keil MDK自动使用对象包含enum范围的最小整型来实现(比如char类型),除非使用编译器命令enum_is_int 来强制将enum的基础类型设为至少和整型一样宽。超出范围的枚举值默认仅产生警告:#66: enumeration value is out of "int" range
13. 结构体:
struct {
char c;
short s;
int x;
} //这个结构体占8个字节
但是,结构体:
struct {
char c;
int x;
short s;
} //这个结构体占12个字节
这是为什么?
对于结构体填充,据定义结构的方式,keil MDK编译器用以下方式的一种来填充结构:
定义为static或者extern的结构用零填充;
栈或堆上的结构,例如,用 malloc() 或者 auto定义的结构,使用先前存储在那些存储器位置的任何内容进行填充。不能使用memcmp()来比较以这种方式定义的填充结构!
14. 编译器不对声明为volatile 类型的数据进行优化。 我发现还有不少刚入门的嵌入式程序员从没见过这个关键字.
15. __nop():延时一个指令周期,编译器绝不会优化它。如果硬件支持NOP指令,则该句被替换为NOP指令,如果硬件不支持NOP指令,编译器将它替换为一个等效于NOP的指令,具体指令由编译器自己决定。