前言
这是一个逻辑上的疏忽,一般来讲我们常用的数都是有符号位的,稍不注意就容易出现无符号计算的漏洞。
两个有符号正数相减为负数时,当他们为无符号数时,结果应当为一个很大的无符号数。
但在运算时,小于int的无符号数可能会出现隐式符号转换(转变成有符号的数进行计算,得到结果为负数)。
以下例子中我们可以很清楚的得出以上的结论。
代码语言:javascript复制// 1
unsigned char a = 1;
unsigned char b = 2;
if (a - b < 0) // a - b = -1 (signed int)
a = 6;
else
a = 8;
// 2
unsigned char a = 1;
unsigned short b = 2;
if (a - b < 0) // a - b = -1 (signed int)
a = 6;
else
a = 8;
上述结果均为a=6
代码语言:javascript复制// 3
unsigned int a = 1;
unsigned short b = 2;
if (a - b < 0) // a - b = 0xffffffff (unsigned int)
a = 6;
else
a = 8;
// 4
unsigned int a = 1;
unsigned int b = 2;
if (a - b < 0) // a - b = 0xffffffff (unsigned int)
a = 6;
else
a = 8;
上述结果均为a=8
如果预期为8,则错误代码:
代码语言:javascript复制// Bad
unsigned short a = 1;
unsigned short b = 2;
if (a - b < 0) // a - b = -1 (signed int)
a = 6;
else
a = 8;
正确代码:
代码语言:javascript复制// Good
unsigned short a = 1;
unsigned short b = 2;
if ((unsigned int)a - (unsigned int)b < 0) // a - b = 0xffff (unsigned short)
a = 6;
else
a = 8;
避免隐式符号转换
像前面代码中所写的,在判断语句中增加无符号声明 if ((unsigned int)a - (unsigned int)b < 0)
,此外,我们还可以这样写:
声明另一个无符号变量 c
去约束计算式子。
// Good
unsigned short a = 1;
unsigned short b = 2;
unsigned short c = a - b; // a - b = 0xffff (unsigned short)
if (c < 0)
a = 6;
else
a = 8;
在不声明新变量的情况下,我们可以直接使用 a
变量,其本身就是一个无符号变量。
// Good
unsigned short a = 1;
unsigned short b = 2;
a = a - b; // a - b = 0xffff (unsigned short)
if (a < 0)
a = 6;
else
a = 8;
规避处理
对于这种预期外的结果,我们还可以通过一些处理逻辑进行规避。
比如在环形缓冲区的使用场景中,我们使用无符号整数去计算索引距离时,可以通过总缓冲区大小来说明大的无符号结果。
在进行计算时我们先判断无符号变量大小,再作判断,即可避免产生一个大的无符号数,得到期望的结果。
代码语言:javascript复制unsigned int buffer[256];
unsigned int head = 10;
unsigned int tail = 250;
unsigned int distance;
if (head >= tail) {
distance = head - tail;
} else {
distance = (256 - tail) head;
}
总结
- 无符号整数减法:
- 当两个无符号整数相减,结果为负数时,结果会被解释为一个很大的无符号数。
- 例如,
unsigned char a = 1; unsigned char b = 2; a - b
的结果是0xFF
(即 255),因为无符号整数不能表示负数。
- 隐式类型转换:
- 在 C/C 中,算术运算符会将较小的无符号类型提升为
int
或者unsigned int
来进行运算。这可能会导致一些意外的结果。
- 在 C/C 中,算术运算符会将较小的无符号类型提升为