724 字
4 分钟
[编程] C 语言陷阱:无符号数减法
在 C 语言编程中,类型选择不当往往会引发难以察觉的严重 Bug。本文将通过一个简单的数组求和函数,解析无符号数(unsigned)与隐式类型转换结合时产生的“访问违例”异常。
1. 现象描述
观察以下数组求和函数:
int sum(int a[], unsigned len) { int i, sum = 0; for (i = 0; i <= len - 1; i++) sum += a[i]; return sum;}异常表现当参数
len = 0时,执行该函数会发生 Access Violation (访问违例)。 然而,如果将len的类型说明为int型,函数却能正确执行并返回 0。
2. 深度原因分析
问题的根源在于 无符号数的减法溢出(回绕) 与 C 语言的隐式类型转换规则。
2.1 无符号数的“回绕” (Wrap-around)
在计算机中,无符号数没有负数概念。当 len = 0 且类型为 unsigned 时,表达式 len - 1 的计算过程如下:
- 二进制表示:
000...000 - 000...001 - 结果(补码形式):
111...111 - 对于
unsigned类型,这个全 1 的二进制串被解释为该类型的最大正整数(例如在 32 位系统下是4,294,967,295)。
2.2 隐式类型转换 (Usual Arithmetic Conversions)
循环条件为 i <= len - 1。
i的类型是int(有符号)。len - 1的类型是unsigned int(无符号)。
C 语言转换规则当一个有符号整数(
int)与一个无符号整数(unsigned int)进行算术运算或比较时,有符号整数会被隐式转换为无符号整数。
2.3 逻辑执行过程
i初始值为 0。- 比较
i <= len - 1。 i被转换为unsigned(0),len - 1为unsigned(4294967295)。- 比较结果:
0 <= 4294967295永远成立。 - 循环持续执行,访问
a[0], a[1], a[2]...最终因访问了操作系统保护的非法内存地址而触发访问违例。
3. 为什么 int 类型能正确执行?
当 len 为 int 类型且等于 0 时:
len - 1得到-1(有符号运算)。- 比较条件为
i <= -1。 - 初始值
0 <= -1为假(False)。 - 循环体一次也不执行,直接返回
sum = 0,逻辑正确。
4. 最佳实践与防坑指南
这种错误在底层开发中极具隐蔽性。为了编写更健壮的代码,建议:
修正建议避免在循环条件中使用减法表达式,改用更直接的边界判断:
// 无论 len 是有符号还是无符号,这种写法都是 100% 安全的for (i = 0; i < len; i++) {sum += a[i];}当
len = 0时,0 < 0为假,循环会安全跳过。
总结
这个案例警示我们:
- 谨慎使用
unsigned处理可能为 0 的计数逻辑。 - 永远警惕有符号数与无符号数混合比较产生的隐式转换。
- 优先选择
i < len而非i <= len - 1。
[编程] C 语言陷阱:无符号数减法
https://www.eustia-astraea.top/posts/programming/c-unsigned-trap/