(1)注意数据类型及其范围
不同数据类型的表示方法和范围是不同的,整型如下图:
这里要注意符号数和无符号数是有区别的,符号的最高位要牺牲出来作为符号位,符号位为1表示负数,符号位为0表示正数。实质上,我们对内存中的数据进行解释,是按照他的数据类型进行解释的。举个例子,-3在内存中的补码(假设8位)表示为1000 0011,如果定义-3为无符号型,那么解释得到的数值就是131。所以,有时候会发生一些看似“奇怪”的事情。举两个例子:
char的表示范围是-127~127,这里定义char a=150,超过了其表示范围,编译器就会按照符号数进行处理,所以最后结果是负数。
(2)注意不同数据类型的转换
我们再来看例外一个例子:
按理说,最后的结果应该是i < sizeof(array)啊?
原来是这样,sizeof返回值是size_t类型,是一个无符号型。当有符号数和无符号数进行运算时,有符号数会转换成无符号数。这样,-1用无符号数表示的话就是一个非常大的正数。所以会出来“看似奇怪”的结果。
由此可见,有符号数和无符号数混合使用是危险的,所以在代码中要尽量少使用无符号型。
(3)防止无符号整数回绕
涉及无符号数的计算是永远不会发生溢出的,也就是说,如果数值超过了无符号数的最大上限,就会发生回绕。如果无符号数超过了最大值,就会返回0,然后从0开始增大;如果低于无符号数的下限,那么就会达到无符号数的上界,然后从上界开始减小。举个例子:
因此,在无符号数的运算中,要采取适当的方法防止回绕。例如做如下修改:
#include<stdio.h>
int main(){
unsigned int a = 4294967295;
unsigned int b = 2;
unsigned int c = 4;
if(UINT_MAX - a > b)
printf("%u\n",a+b);
if(UINT_MIN + c < b)
printf("%u\n",b-c);
return 0;
}
(4)防止符号整数溢出
当两个符号数运算溢出时,会导致“不确定的行为”,大多数编译器会忽略溢出,这样就会导致不确定或错误的值保存在变量中。溢出跟回绕类似,会使变量的值发生“意想不到”的变化,可以逃过边界检查,导致很多问题,比如缓冲区溢出。
(5)少使用浮点数
大多数平台下,浮点数都遵循IEEE754标准。浮点类型在不同平台下的实现差异很大,有的平台有浮点运算单元(FPU);有的处理器只能做整型运算,需要用整数运算实现模拟浮点运算(软浮点)。
5.1 避免浮点数进行精确计算,用分数精确表示浮点数,避免浮点数因舍入误差导致的不精确问题。
5.2 避免浮点数直接使用==进行判断,一般采用|V0 - V1 < MIN|的形式。
5.3 避免使用浮点数作为循环计数器。
5.4 尽量将浮点运算中的整数转换为浮点数。看个例子:
为什么第一个结果跟后面四个不一样呢?而且第一个结果明显精确度不高。因为x/9会首先执行,然后将结果35转换成浮点数赋给a。还有一点要注意,整数与浮点数运算,会将整数首先转化为浮点数。因此,为了避免这种信息缺失的情况,我们使用整数运算并把结果赋给一个浮点数时,要首先将一个整数转换成浮点数!
(6)数据类型转化时做范围检查
不同类型的数据进行转化,要先转化为级别最高的数据类型,然后再进行运算。例如int类型的数据和long类型的数据运算,int先要转化成long。还有一个要注意的是,所有的浮点操作都是双精度的,即使表达式中只有float类型,也要首先转化成double类型。
如果一个无符号类型转化成有符号类型,就有可能发生高位截断而数据丢失,主要是防止溢出。
(7)使用有严格定义的数据类型
首先看两个例子:
为什么会这样呢?因为ch与0xff比较时,首先将ch转换为int型,如果int是32位的,那么如果ch是有符号的,那么补为0xffff ffff;如果ch是无符号的,那么补为0x0000 00ff。
尽量使用严格形式定义的、可移植的数据类型,尽量不使用与具体硬件或软件关系密切的变量。