有符号 short 溢出(有符号整数溢出属于未定义行为)
c
#include <stdio.h>
int main() {
short num = 0x7FFF;
printf("%hd \r\n", num);
num = 0x8000;
printf("%hd \r\n", num);
return 0;
}
- short:2 字节 = 16bit,默认signed有符号
○ 范围:-2^15 ~ 2^15-1,即 -32768 ~ 32767
○ 0x7FFF:二进制0111 1111 1111 1111 → 32767(最大值)
○ 0x8000:二进制1000 0000 0000 0000 → -32768(最小值) - 调试自增演示
c
short num = 32767;
num++; // 32767 +1,二进制进位,最高位变成1
printf("%hd", num); // 输出 -32768
● 汇编:movsx eax, word ptr num
● movsx:符号位扩展,把 16 位 short 扩展到 32 位 eax
● 最高位是 1,代表负数;最高位 0 代表正数
⚠️重点:C 标准里有符号整数溢出是 UB(未定义行为),只是在 VC/VS 编译器下会按补码回绕,不同编译器 / 优化等级结果可能不一样
无符号 unsigned int 溢出(标准定义:模 2^n 取余,行为确定)
c
#include <stdio.h>
int main() {
unsigned int num = 4294967295;
printf("%u \r\n", num);
num++;
printf("%u \r\n", num);
return 0;
}
- unsigned int:4 字节 = 32bit,无符号
○ 范围:0 ~2^32-1,最大值0xFFFFFFFF = 4294967295
○ 0xFFFFFFFF + 1 = 0x100000000
○ 只有 32 位存储空间,丢弃进位的最高位 1,剩余0x00000000,结果 = 0
✅重点:无符号整数溢出是 C 标准明确定义的,自动对2^32取模,一定会回绕
反汇编核心片段解析
c
mov dword ptr [num],0FFFFFFFFh ; 给num赋值0xFFFFFFFF
mov eax,dword ptr [num]
push eax
push offset string "%u \r\n"
call _printf
add esp,8
mov eax,dword ptr [num]
add eax,1 ; eax = eax + 1
mov dword ptr [num],eax ; 写回栈,超出32bit的进位直接丢弃
● add eax,1 加法指令本身会产生进位标志 CF,但存储到 32 位变量时,超出的进位不会保存,直接截断
对比总结表
| 类型 | 字节 | 溢出行为 | C 标准规定 |
|---|---|---|---|
| signed short | 2 | 32767+1 → -32768 | 未定义行为 UB,仅 VC 下补码回绕 |
| unsigned int | 4 | 4294967295+1 → 0 | 确定,模2^32截断 |
补充易错点
- 不要依赖有符号数溢出回绕写代码,开启 O2 优化后结果可能完全改变
- 溢出本质:固定 bit 宽度,加法进位超出存储空间,发生截断;有符号还要额外解释最高位为符号位
- movsx(符号扩展)vs movzx(零扩展)
○ movsx:有符号类型扩展,高位补符号位
○ movzx:无符号类型扩展,高位补 0