1.1 必须用typedef显式标识出各数据类型的长度和符号特性,避免使用标准数据类型。
例如,一个32位整数系统,可定义如下:
typedef char char_t; typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; typedef signed long int64_t;
typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef unsigned long uint64_t; |
这是因为不同的编译器对标准数据类型的长度定义不一样,用int8_t等来定义数据类型,一方面增加了程序的可读性,使程序员对程序中的数据的具体信息胸有成竹;另一方面也有助于程序在不同系统之间移植,节省开发时间,减少隐患。
1.2 数据类型转换
1.2.1 如果位操作符~和移位操作符<<(或>>)联合作用于unsigned char 或unsigned short类型的操作数时,中间运算步骤的结果必须立刻显示强制转换为预期的短整型数据类型。
考虑下面一段程序,result_8的值会是多少呢?
/* 注: uint8_t表示8位无符号整型 */ uint8_t port = 0x5a; uint8_t result_8; result_8 = (~port) >> 4; |
整数操作数的“平衡”原则: 对于隐式表达式, 编译器会按照既定规则对操作数进行位数扩充, 其中int和unsigned int在整型表达式“平衡”过程中占重要地位.
下面分析一个简单的隐式整型表达式c = a + b (假设a 的存储位数不大于b 的存储位数), 编译器是这样来处理这个表达式的:
如果b 是短整型( 即位数少于int, 比如char 、short等) 或者整型(int 或unsigned int), 那a 也是短整型或者整型, 执行“+”运算之前, a 和b 都将被扩充为整型(int 或者unsigned int) , 然后相加的结果赋给c (如果c不是int 或者unsigned int 类型, 则这个赋值操作也会包含隐式的扩充或截断操作) .
如果b 是长整型(存储位数多于int ), 则a 会被扩充为与b 相当的长整型, 再执行“+”运算, 所得结果赋给c(可能包含隐式的扩充或截断操作) .
绝大多数的操作符用于整型运算的时候, 都遵循上述“平衡”原则, 比如: 算术操作符、位操作符和关系操作符.
但逻辑操作符不遵循上述“平衡”原则. 此外左移(<<)和右移(>>)运算符也不遵循“平衡”原则, 只和移位操作符左边的整型操作数相关.
1.2.2 以下情况下,整型表达式中不允许出现隐式数据类型转换:
a) 整型操作数不是被扩充为更多位数的同符号整数;
b) 表达式是复杂表达式;
c) 表达式不是常数表达式,且是函数的参数;
d) 表达式不是常数表达式,且是函数的返回表达式。
考虑下面一段程序,d 的值会是多少呢?
/* 注: uint16_t表示16位无符号整型, uint32_t表示32位无符号整型 */ uint16_t a = 10; uint16_t b = 65531; uint32_t c = 0; uint32_t d; d = a + b + c; |
如果用一个32 位的编译器来编译这段程序, 最终结果是d = 65541, 程序员“幸运地”得到了预期的结果。如果是16 位的编译器, 得到的结果却是d = 5。
由于“+”运算是左结合的,所以d = a + b + c 等效于d = (a + b) + c , 即先执行a + b ,所得的和再与c 相加,最后结果赋值给d。问题就出在a + b 这个中间步骤中。由于a 和b 都是16 位整型(注意编译器也是16 位的) ,故而a + b的结果也是16 位整型,则a + b 的值是0x0005 (有溢出); 再扩充为32 位整型0x00000005 和c 相加赋值给d ,d = 5, 这并非程序员预期的结果。
1.2.3 以下情况下,浮点数表达式中不允许出现隐式数据类型转换:
a) 浮点数操作数不是被扩充为更多位数的同符号浮点数;
b) 表达式是复杂表达式;
c) 表达式是函数的参数;
d) 表达式是函数的返回表达式。