目录
- 线索栏
- 笔记栏
-
- 1. 隐式转换的陷阱与核心警告
- 2. 练习题 2.25 (数组求和 Bug)
-
- 1)Bug代码
- 2)错误分析
- 3)修复方案
- 3. 练习题 2.26 (字符串比较 Bug)
-
- 1)Bug代码
- 2)错误分析
- 3)修复方案
- 4. 真实漏洞分析:FreeBSD getpeername
-
- 1)漏洞代码简化
- 2)漏洞成因
- 3)修复方法
- 5. 核心建议与合理使用场景
-
- 1)核心建议(黄金法则)
- 2)合理且有益的特定场景
- 总结栏
线索栏
笔记栏
1. 隐式转换的陷阱与核心警告
(1)核心警告:C语言中,从有符号数到无符号数的隐式强制类型转换是许多非直观行为和程序错误的根源。由于转换悄无声息地发生,程序员极易忽视其影响,导致难以发现的细微错误。 (2)原理:当有符号数和无符号数在表达式中混合运算时,C语言会将有符号数隐式地转换为无符号数,然后按无符号规则进行运算。这会导致负数被解释为极大的正数,彻底改变比较和算术运算的逻辑。
2. 练习题 2.25 (数组求和 Bug)

1)Bug代码
float sum_elements(float a[], unsigned length) {
int i;
float result = 0;
for (i = 0; i <= length – 1; i++)
result += a[i];
return result;
}
2)错误分析
(1)当 length = 0时,表达式 length – 1的结果应为 -1。 (2)但由于 length是 unsigned类型,整个表达式会进行无符号运算:0U – 1U。 (3)在无符号模运算下,0U – 1U的结果并非 -1,而是通过下溢得到最大值 UMax(对于32位是4294967295)。 (4)循环条件 i <= UMax恒成立,导致程序试图访问数组 a的非法内存(a[0], a[1], …),最终引发内存错误。
3)修复方案
(1)最佳方案:将循环条件改为 i < length。这是最常见且安全的写法。 (2)次选方案:将参数 length的类型改为 int。但会改变函数接口。 
3. 练习题 2.26 (字符串比较 Bug)

1)Bug代码
int strlonger(char *s, char *t) {
return strlen(s) – strlen(t) > 0;
}
2)错误分析
(1)strlen的返回类型是 size_t(在标准库中通常定义为 unsigned int或 unsigned long)。 (2)当 strlen(s) < strlen(t)时,表达式 strlen(s) – strlen(t)会进行无符号减法,结果是下溢后的大正数,导致比较 > 0恒为真,函数错误地返回 1(表示s更长)。
3)修复方案
(1)直接比较长度,避免做差:return strlen(s) > strlen(t); (2)这是安全可靠且意图清晰的做法。 
4. 真实漏洞分析:FreeBSD getpeername
1)漏洞代码简化
#define KSIZE 1024
char kbuf[KSIZE];
int copy_from_kernel(void *user_dest, int maxlen) { // maxlen 为有符号 int
int len = KSIZE < maxlen ? KSIZE : maxlen;
memcpy(user_dest, kbuf, len); // memcpy的第三个参数 n 是无符号 size_t
return len;
}
2)漏洞成因
(1)数据类型不匹配:函数参数 maxlen为有符号 int,但库函数 memcpy的第三个参数 n是无符号 size_t。 (2)恶意参数触发:攻击者传入一个负值(如 -1)给 maxlen。 (3)隐式转换与下溢:在第16行,负的 maxlen与 KSIZE(1024) 比较时,会先被隐式转换为无符号数,变成一个极大的正数(接近 UMax)。因此 len被赋值为这个极大值。 (4)溢出执行:这个极大的 len作为 size_t传递给 memcpy,导致函数尝试复制海量数据(远超目标缓冲区大小),可能读取未授权的内核内存,造成信息泄露。
3)修复方法
统一类型,消除不匹配:将 copy_from_kernel的参数 maxlen、局部变量 len和返回值类型都改为 size_t。
5. 核心建议与合理使用场景
1)核心建议(黄金法则)
除非在必要且能清晰掌控的场景下,否则尽量避免使用无符号数。 许多现代语言(如 Java、Python)的设计就完全放弃了无符号整数类型,正是为了避免此类隐晦错误。
2)合理且有益的特定场景
(1)位集合与标志:当把变量纯粹当作位的集合来操作时(如设置、清除标志位、掩码运算)。 (2)内存地址:地址计算本质上是模运算,使用无符号数非常自然。 (3)模运算:在需要显式利用模运算特性的算法中。 (4)多精度算术:当大整数需要用基本数据类型的数组来表示时,每个数组元素可以作为无符号的“数字”。
总结栏
本节通过理论分析和实例(练习题、真实漏洞)深刻揭示了无符号数与有符号数混用的巨大风险:
根本启示:计算机系统中的数据类型不仅是值的容器,更是运算规则的契约。无符号数的算术是模运算,而非通常的整数运算。忽视这一语义差异,高层抽象就会“泄漏”,导致逻辑错误和安全漏洞。编写健壮代码的关键在于:理解并尊重每个类型的语义,并在边界处保持警惕。
网硕互联帮助中心



评论前必须登录!
注册