在程序里头经常会用到整数之间的大小比较,但是其中潜在的危险却往往被忽略了。例如一个内存拷贝函数:
void memcpy(void *pTo,void *pFrom,size_t size)
{
assert(pTo != NULL && pFrom != NULL);
while( --size >= 0)
{
*pTo++ = *pFrom++;
}
}
这个函数正确吗?如果你认为它永远都不可能跳出那个该死的循环就对了。size_t是一个无符号整数类型(VC6.0: typedef unsigned int size_t,VC7.1: typedef unsigned __int64 size_t),所以--size得到的结果也是同样类型,而这样一个类型的值永远也不可能小于0!那我们尝试着改进它:
void memcpy(void *pTo,void *pFrom,size_t size)
{
assert(pTo != NULL && pFrom != NULL);
while( size-- > 0)
{
*pTo++ = *pFrom++;
}
}
这个版本中更改了循环条件,使得当size等于0的时候循环结束。危险消除了吗?嗯,消除了大部分,但是还存在着一个令人不愉快的地方。如果有一个int类型的变量len,用它做size的实参来调用memcpy,又假设碰巧len的值小于0,那么会出现什么结果呢?size接收到了一个负值,但因为它本身是无符号型的,所以它会把这个负值解释成为无符号型整数,那必然是个正值,如此一来混乱可想而知。这就是一个典型的有符号/无符号不匹配错误。再看一段代码:
unsigned long a = 0;
long b = 0;
long c = 1;
if( (a - c) < b )
{
// do something
}
if( (a - 1) < b )
{
// do something
}
这段代码的两个if语句的if分支有可能执行吗?答案是绝无可能。因为表达式(a - c)的类型不是long,而是unsigned long,它的值绝不会小于b(即0)。同样地,表达式(a - 1)的值也永远大于等于0。
上面两个例子代表了我们在写程序时经常会犯的错误:无意识地滥用类型不匹配的变量、表达式(尤其是有符号型与无符号型)之间的赋值与比较。
有什么办法能预防这种错误吗?事实上如果你足够警觉,将编译器的编译警告开关设为最高级(比如VC7.1的4级: /W4),那么任何一个负责任的编译器都会在上述情况下明白无误地警告你。顺便说一下,VC工程中默认的编译警告为3级,此时编译器的责任心就大大降低了,对上述的危险情况根本视若无睹。
所以为了让自己能够睡个安稳觉,首先应当尽量避免使用相异类型之间的赋值、比较。然后设定开发工具的检查为最严格等级,让它们能帮我们尽早地嗅出这类潜在的危险。