有符号数与无符号数比较的坑
一个小demo
在c/c++ 的项目编译时经常会遇到 “comp.c:59:42: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]” 这种错误。作为一个”合格的程序员“ 对这种编译告警,通常的处理是忽略,毕竟大家一致的观点是:只有“warning”不算问题!
下面给出一个小case:
```c
#include <stdio.h>
int sum_elements(int a[], unsigned int length)
{
int i;
int result = 0;
for (i = 0; i <= length - 1; i++) // 故意写成 length - 1
result += a[i];
return result;
}
int main(int argc, char *argv[])
{
int a[] = {1, 2, 3};
int m = sum_elements(a, 0); // 故意传 0
printf("%d\n", m);
return 0;
}
一路轻车熟路,编译运行,但是得到了一个段错误。。。
➜ ~ c++ -Wall -g xx.c
xx.c: 在函数‘int sum_elements(int*, uint32_t)’中:
xx.c:7:23: 警告:comparison of integer expressions of different signedness: ‘int’ and ‘uint32_t’ {aka ‘unsigned int’} [-Wsign-compare]
7 | for (int i = 0; i <= length - 1; i++)
| ~~^~~~~~~~~~~~~
➜ ~ ./a.out
Segmentation fault (core dumped)
➜ ~
如上一个漏洞百出的 demo,暴露了一个很容易忽略的坑。
无符号数带来的问题
如前面示例所示,无符号数与有符号数比较时带来的问题是最容易忽略的,也有可能是最致命的。如果循环中出现无符号数可能会引起死循环,数组中出现无符号可能会导致数组越界。这当然不能把问题归咎于无符号数,更应该说是有符号数带来的思维定势引起的问题。
无符号数带来的问题通常是在数值比较时,与无符号数溢出时才会体现。分别是默认的类型转换与边界条件检测不正确导致的。
既然无符号会有问题,那么我们是不是可以抛弃呢??? 你可以采用java那套做法,在习惯上从不使用无符号数(java有这种方案),但是你会在默写特定场景遇到问题:如网络编程,串口读写。。。
如何合理的使用无符号数?这是问题的核心了!下面是我总结的一些内容:
-
在位运算、模运算、等利用较多的算法实现中(比如各种加密学算法、编码、压缩算法等)使用无符号数
有符号数的符号位在进行位运算时候会造成一些迷惑,位运算中如果采用无符号数会大大减少处理问题时对语言上的思考,可以更专心关注实际问题。 -
在网络收发,串口读写时候使用无符号数
TCP/IP 经常遇到无符号数,比如 IP 的表示,我们可以用 ip2long 把点分十进制 ip 转成一个 unsigned int 来表示,这会带来很大方便。串口读写的流更多的是用 unsigned char ,最常见的一个问题是 unsigned char 可以避免日志输出时候按照有符号输出造成的 ‘0xff’ 迷惑人的前缀。 -
避免有符号数与无符号数的直接接触,包括比较、数值运算
无符号数与有符号数比较时,编译器会发出警告。同时编译器内部也存在一套默认的类型转换规则(编译器自动进行,用户无感知)。大致分为3类(如有错误请指正)(说明:在计算机里,负数使用反码表示的)
先定一下规则:
-
有符号(int)
-
无符号(unsigned int)
-
非无符号(如 unsigned char)
-
非有符号(如 char)
有符号与无符号比较:有符号数会转换成无符号数来进行比较(如int 与 unsigned int 比较,int 转换成 unsigned int)。
有符号与非无符号数比较:非无符号转化成有符号(如int 与 unsigned char比较,unsigned char 转换成 int)。
无符号与非有符号数比较:非有符号转化成有符号(如unsigned int 与 char比较,char 转换成 unsigned int)。
-
不要只因为某个数不可能为负就用无符号数
因为这虽然看起来很合要求,但是当无符号溢出时候带来的问题却很可能致命。