C语言规定:不同类型的数据(比如char和int型数据)需要转换成同一类型后,才可进行计算。
当出现在表达式里时,有符号和无符号的char和short类型都将自动被转换为int类型,在需要的情况下,将自动被转换为unsigned int(在short和int具有相同大小时)。这称为类型提升。
1.提升在算数运算中通常不会有什么大的坏处,但如果位运算符 ~ 和 << 应用在基本类型为unsigned char或unsigned short 的操作数,结果应该立即强制转换unsigned char或者unsigned short类型(取决于操作时使用的类型)
1 | uint8_t port =0x5aU; |
2.在包含两种数据类型的任何运算里,两个值都会被转换成两种类型里较高的级别。类型级别从高到低的顺序是long double、double、float、unsigned long long、long long、unsigned long、long、unsigned int、int
1 | uint16_t u16a = 40000; /* 16位无符号变量*/ |
以上表达式错误的原因是uint16_t类型溢出不能存放70000,正确的解决方法是将表达式的一个或者多个变量提示到(uint32_t)
1 | u32x = (uint32_t)u16a + u16b; // 计算表达式时因为表达式最大的类型为(uint32_t),会在计算表达式之前将所有的变量类型提升到(uint32_t) |
3.在赋值语句里,计算的最后结果被转换成将要被赋予值的那个变量的类型。这一过程可能导致类型提升也可能导致类型降级,出现类型截断
1 | uint16_t u16a = 4000; /* 16位无符号变量*/ |
4.当作为函数的参数被传递时,char和short会被转换为int,float会被转换为double
5.有符号和无符号进行强制转换时应注意符号位
- 并非所有强制类型转换都是由风险的,把一个整数值转换为一种具有相同符号的更宽类型时,是绝对安全的。
- 精度高的类型强制转换为精度低的类型时,通过丢弃适当数量的最高有效位来获取结果,也就是说会发生数据截断,并且可能改变数据的符号位。
- 精度低的类型强制转换为精度高的类型时,如果两种类型具有相同的符号,那么没什么问题;需要注意的是负的有符号精度低类型强制转换为无符号精度高类型时,会不直观的执行符号扩展
1 | unsigned int bob; |
当表达式中有有符号和无符号都会转换看成无符号数,补码就是为了解决有无符号的问题,使得cpu的计算不用考虑符号,变成单纯的二进制加法运算(减法运算就相当于加一个负数)。
1 | int16_t i = -96; |
类型提升补充示例:
1 | char c = 1; |
神奇的volatile
volatile限定符用来告诉编译器,该对象的值无任何持久性,不要对它进行任何优化;它迫使编译器每次需要该对象数据内容时都必须读该对象,而不是只读一次数据并将它放在寄存器中以便后续访问之用(这样的优化可以提高系统速度)。
这个特性在嵌入式应用中很有用,比如你的IO口的数据不知道什么时候就会改变,这就要求编译器每次都必须真正的读取该IO端口。这里使用了词语“真正的读”,是因为由于编译器的优化,你的逻辑反应到代码上是对的,但是代码经过编译器翻译后,有可能与你的逻辑不符。你的代码逻辑可能是每次都会读取IO端口数据,但实际上编译器将代码翻译成汇编时,可能只是读一次IO端口数据并保存到寄存器中,接下来的多次读IO口都是使用寄存器中的值来进行处理。因为读写寄存器是最快的,这样可以优化程序效率。与之类似的,中断里的变量、多线程中的共享变量等都存在这样的问题
每次都从外设的IO输入寄存器读数据,而不是使用上一次从外设IO输入寄存器读过来的值,这些值都会被保存在栈中或者CPU寄存器中。
外设IO寄存器相对CPU来说是外部的,CPU的寄存器是内部的。
MDK编译器的一些小知识
默认情况下,char类型的数据项是无符号的,所以它的取值范围是0~255;
在所有的内部和外部标识符中,大写和小写字符不同;
通常局部变量保存在寄存器中,但当局部变量太多放到栈里的时候,它们总是字对齐的。
压缩类型的自然对齐方式为1。使用关键字__packed来压缩特定结构,将所有有效类型的对齐边界设置为1;
整数以二进制补码形式表示;浮点量按IEEE格式存储;
整数除法的余数的符号于被除数相同,由ISO C90标准得出;
如果整型值被截断为短的有符号整型,则通过放弃适当数目的最高有效位来得到结果。如果原始数是太大的正或负数,对于新的类型,无法保证结果的符号将于原始数相同。
整型数超界不引发异常;像unsigned char test; test=1000;这类是不会报错的;
在严格C中,枚举值必须被表示为整型。例如,必须在‑2147483648 到+2147483647的范围内。但MDK自动使用对象包含enum范围的最小整型来实现(比如char类型),除非使用编译器命令‑‑enum_is_int 来强制将enum的基础类型设为至少和整型一样宽。超出范围的枚举值默认仅产生警告:#66:enumeration value is out of “int” range;
对于结构体填充,根据定义结构的方式,keil MDK编译器用以下方式的一种来填充结构:
I> 定义为static或者extern的结构用零填充;
II> 栈或堆上的结构,例如,用malloc()或者auto定义的结构,使用先前存储在那些存储器位置的任何内容进行填充。不能使用memcmp()来比较以这种方式定义的填充结构!
编译器不对声明为volatile类型的数据进行优化;
__nop():延时一个指令周期,编译器绝不会优化它。如果硬件支持NOP指令,则该句被替换为NOP指令,如果硬件不支持NOP指令,编译器将它替换为一个等效于NOP的指令,具体指令由编译器自己决定;
__align(n):指示编译器在n 字节边界上对齐变量。对于局部变量,n的值为1、2、4、8;
attribute((at(address))):可以使用此变量属性指定变量的绝对地址;
__inline:提示编译器在合理的情况下内联编译C或C++ 函数;
一种是映象文件位于存储器时(通俗的说就是存储在Flash中的二进制代码)的地址,称为加载地址;一种是映象文件运行时(通俗的说就是给板子上电,开始运行Flash中的程序了)的地址,称为运行时地址
初始化信息的保存和恢复
公司目前使用的4.3寸LCD显示屏抗干扰能力一般。如果显示屏与控制器之间的排线距离过长或者对使用该显示屏的设备打静电或者脉冲群,显示屏有可能会花屏或者白屏。对此,我们可以将初始化显示屏的数据保存在Flash中,程序运行后,每隔一段时间从显示屏的寄存器读出当前值和Flash存储的值相比较,如果发现两者不同,则重新初始化显示屏:
1 | typedef struct { |
- 本文作者: 龙兄嵌入式
- 本文链接: https://hexo.880755.xyz/1970/01/01/zblog/download/102.C语言的陷阱/