一段计时代码在测试机上跑得好好的,上线后循环停不下来。查了半天,是 for (unsigned i = n; i >= 0; i--) 这一句。
unsigned 是无符号整型,只能表示非负数。C 语言里 unsigned int 的范围是 0 到 4294967295,减到 0 之后再减 1,不会变成负一,而是回绕成最大值。
回绕是最经典的坑
上面那个循环,i 到 0 时执行完循环体再减 1,变成 4294967295,条件 i >= 0 永远成立,直接死循环。编译器通常会给出 warning,但很多人根本不看。
倒序遍历无符号下标,可以把判断和自减合并:
for (size_t i = n; i-- > 0; ) { }或者干脆用有符号的 int,只要范围够。别指望靠 i != -1 之类的写法兜底,比较时类型还是会转换。
和无符号比较时的整型提升
int 和 unsigned int 一起比较,int 会被转成 unsigned。所以 -1 小于 1u 这个判断是假的,因为 -1 变成了 4294967295。这类 bug 在判断长度和返回值时特别隐蔽:
if (strlen(s) - 1 >= 0) { }strlen 返回 size_t,减 1 之后永远大于等于 0,条件恒真。空串时 strlen(s)-1 变成最大值,后面按这个长度取数据就越界了,栈上直接踩坏。
unsigned char 不是拿来存字符的
它常用来处理二进制数据:图片像素、协议报文、哈希值。因为范围是 0 到 255,读字节不会出现符号扩展。用 char 接 0xFF 再转 int,可能变成 -1,做 CRC 计算时结果全错,这种问题很难从日志里看出来。
打印的时候也要配对,unsigned 用 %u,size_t 用 %zu。用 %d 打印大值,输出会变成负数。
该用在哪
位运算、哈希、内存大小、数组下标,用无符号合适,语义清楚还能多一位范围。涉及减法、差值、可能为负的中间结果,老老实实用有符号。
编译时开 -Wall -Wextra,sign-compare 警告基本能把这类问题捞出来。别嫌警告多,关掉警告的项目里,unsigned 的坑往往要等到线上才炸。