目录
[1. 无符号 unsigned 整数](#1. 无符号 unsigned 整数)
[2. 有符号 signed int](#2. 有符号 signed int)
[3. 硬件层面溢出判断](#3. 硬件层面溢出判断)
[1. 正数(符号位 = 0)](#1. 正数(符号位 = 0))
[2. 负数(符号位 = 1)](#2. 负数(符号位 = 1))
[3. 0 的表示](#3. 0 的表示)
[示例 1:计算 5 - 3,等价 5 + (-3)](#示例 1:计算 5 - 3,等价 5 + (-3))
[示例 2:计算 3 - 5,等价 3 + (-5)](#示例 2:计算 3 - 5,等价 3 + (-5))
[溢出判断(4 位例子)](#溢出判断(4 位例子))
[1. 有符号数 signed(符号扩展)](#1. 有符号数 signed(符号扩展))
[2. 无符号数 unsigned(零扩展)](#2. 无符号数 unsigned(零扩展))
[3. C 语言代码现象](#3. C 语言代码现象)
补码运算、整数溢出
计算机 CPU 内部没有独立减法硬件电路。 减法的硬件实现公式:a - b = a + (-b的补码),加减法全部复用加法器电路。
1. 无符号 unsigned 整数
无符号数运算超出范围,遵循模运算
C 标准行为是确定的 。 以 32 位unsigned int举例:
取值范围
最大值+1,溢出回卷,结果直接变成 0。
c
cpp
#include <cstdio>
int main(void)
{
unsigned int u = 4294967295U;
u = u + 1;
printf("%u\n", u); //输出 0
return 0;
}
2. 有符号 signed int
32 位 int 范围:,即
-2147483648 ~ 2147483647。
C 语言标准规定,有符号整数溢出属于未定义行为。 编译器有权优化,不一定一定看到翻转负数;VS 调试运行会观察到溢出翻转。
c
cpp
#include <cstdio>
int main(void)
{
int a = 2147483647;
a = a + 1;
printf("%d\n", a); //VS下变成负数
return 0;
}
3. 硬件层面溢出判断
- 两个正数相加,结果变成负数 → 正溢出
- 两个负数相加,结果变成正数 → 负溢出
重要结论:一正一负两个数相加,永远不会溢出
对比表格
表格
| 类型 | 溢出行为 | C 语言标准定义 |
|---|---|---|
| unsigned | 溢出模\(2^N\)自动回卷 | 行为确定 |
| signed int | 结果不确定,编译器可优化 | 未定义行为 |
做计组硬件计算题,按补码规则直接推演溢出;写 C 代码时,不能依赖有符号数溢出的结果。

思考
- 32 位
unsigned int = 4294967295,再加 1 结果是多少? - C 语言标准中,有符号 int 溢出,行为是否确定?
- 正数和负数相加,会不会发生溢出?
:
1.0
2.不确定
3.不会
:
✅
- ✔ 32 位 unsigned 最大值 + 1,模
,结果为 0
- ✔ C 标准:有符号 int 溢出属于未定义行为,行为不确定
- ✔ 一正一负相加,不可能溢出(硬件层面,结果一定落在表示范围内)
简要
- unsigned 溢出:标准明确定义,模运算回卷,可写代码验证
- signed 溢出:写代码不要依赖溢出后的结果,编译器优化后行为会变;计组做题时,按补码二进制去判断溢出
- 溢出判断口诀:同号相加才有可能溢出,异号相加不会溢出
原码、反码、补码换算规则
计算机存储和运算使用补码,但是做题时经常需要在真值、原码、反码、补码之间互相转换。下面以8 位二进制举例。
最高位是符号位:0 代表正数,1 代表负数;剩余比特是数值位。
1. 正数(符号位 = 0)
✅原码 = 反码 = 补码 例:+5,8 位 原码:0000 0101 反码:0000 0101 补码:0000 0101
2. 负数(符号位 = 1)
转换步骤:
- 写出真值绝对值的原码
- 保持符号位不变,数值位全部取反 → 反码
- 反码 + 1 → 补码
例:-5,8 位
- 绝对值 + 5 原码:
0000 0101 - 加上负号原码:
1000 0101 - 反码:
1111 1010 - 补码:
1111 1011

3. 0 的表示
- 原码:存在
+0(00000000)、-0(10000000)两种表示 - 补码:0 只有唯一编码
00000000多余的编码10000000用来表示额外负数,8 位补码范围:
实操代码
c
cpp
#include <stdio.h>
void print_bin(char val)
{
for(int i=7;i>=0;i--)
{
putchar( (val & (1<<i)) ? '1' : '0' );
}
putchar('\n');
}
int main(void)
{
char a = -5;
print_bin(a);
return 0;
}
思考
- 8 位,
+3的原码是多少? - 8 位,
-3的补码是多少? - 8 位补码为什么可以表示到 - 128?
:
1.00000011
2.11111101
3.-128 -> 原码11111111->反码10000000->补码100000001 ?
:
✅
- ✔ 8 位 + 3 原码:
00000011 - ✔ 8 位 - 3 补码:
11111101 - ❌这里出错了。
重点:-128 不存在 8 位原码和反码,不能用普通负数的转换方式推导。
原理
8 位一共 256 种编码。 0:00000000,占用 1 个编码。 剩下 255 个编码。正数:1~127(127 个);负数本应 127 个。 但是补码把10000000这个编码单独拿出来,规定代表 -128。
10000000没有对应的原码、反码- 8 位补码范围:-128 ~ 127
你刚才写的推导:
原码 11111111→反码 10000000→补码 100000001 这是错误的,
11111111是 - 1 的原码。
一句话:
8 位二进制编码 10000000,在补码里直接定义为 - 128,不能套用 "原码取反加 1" 的常规负数换算公式。
补码加减法实例,进位与舍弃
核心公式:减法,全部转换成加上减数负数的补码。 相加产生超出位数的最高进位,直接丢弃(硬件自动截断)。
下面用4 位补码演示(方便笔算) 4 位补码范围:-8 ~ +7
示例 1:计算 5 - 3,等价 5 + (-3)
- 5的补码 = 0101
- -3的补码 =1101
相加:
plaintext
0101
+ 1101
-------
1 0010
最高位产生进位 1,超出 4 位,直接丢掉。结果0010,真值 = 2 ✔
示例 2:计算 3 - 5,等价 3 + (-5)
1.3补码 = 0011
2.-5补码 = 1011
plaintext
0011
+ 1011
-------
1110
无进位,结果1110,是 - 2 的补码 ✔
溢出判断(4 位例子)
4 位范围 - 8~7
例:
6补码 = 0110
3补码 = 0011
plaintext
0110
+0011
------
1001
两个正数相加,结果最高位为 1(负数)→ 发生正溢出,答案无效。
关键点区分: ✅最高位进位(进位输出丢弃)≠ 溢出 进位只是比特溢出到外面;溢出代表结果超出可表示范围,答案错误,二者不是一回事,考研高频坑!
实操代码(8 位 char 验证)
c
cpp
#include <stdio.h>
void print_bin(char val)
{
for(int i=7;i>=0;i--)
putchar( val & (1<<i) ? '1':'0' );
putchar('\n');
}
int main(void)
{
char a = 5;
char b = -3;
char res = a + b;
print_bin(a);
print_bin(b);
print_bin(res);
return 0;
}

思考
- 补码减法\(A-B\),等价于什么?
- 4 位补码相加,产生超出 4 位的进位,硬件如何处理?
- 二进制相加出现最高进位,是否一定代表溢出?
:
1.A +(-B)
2.截断
3.不一定
:
✅
- ✔ A-B = A+(-B)的补码,减法转为加上负数的补码
- ✔ 超出字长的进位,硬件直接截断丢弃
- ✔ 进位和溢出是两回事:进位是最高位往外多出的比特;溢出代表结果超出当前字长能表达的范围。有进位 ≠ 溢出)
举个简短例子巩固: 4 位补码计算
5-3,相加后产生进位 1,直接丢弃,结果0010=2,有进位,但是没有溢出,结果正确。
符号扩展(位扩展)
短的数据类型,赋值给更长的数据类型,高位如何填充,保证数值不变。
比如:8 位 char → 32 位 int。分有符号 signed 、无符号 unsigned两套规则。
1. 有符号数 signed(符号扩展)
规则:高位全部填充符号位
- 正数符号位 0:高位补 0
- 负数符号位 1:高位补 1
示例:8 位 char -5,补码 11111011,扩展到 16 位 原 8 位:1111 1011 16 位结果:1111 1111 1111 1011 高位全部补上 1,数值仍然是 - 5 不变。
8 位正数+5:00000101 扩展 16 位:00000000 00000101,高位补 0。
2. 无符号数 unsigned(零扩展)
规则:不管正负,高位统一填充0 示例:unsigned char 0xAB(8 位10101011),扩展到 16 位 结果:00000000 10101011,高位全部补 0。
3. C 语言代码现象
c
cpp
#include <stdio.h>
int main(void)
{
char c = -5;
int a = c; //有符号,符号扩展,高位补1
unsigned char uc = 0xAB;
int b = uc; //无符号,零扩展,高位补0
printf("a = %d\n",a);
printf("b = %d\n",b);
return 0;
}
运行结果:
plaintext
a = -5
b = 171
⚠
- 扩展目的:数值保持不变,仅仅增加二进制位数。
- 只有位数变长才存在扩展;位数截断是另外一回事。
- 第一步:先判断是 signed 还是 unsigned,再选择补 1 还是补 0。

思考
- 有符号负数做位扩展,高位补什么?
- unsigned 无符号数位扩展,高位补什么?
- 符号扩展的目标是什么?
:
1.1
2.0
3.增加二进制位数
:
✅
- ✔ 有符号负数位扩展,高位补符号位
1 - ✔ unsigned 无符号数,高位统一补
0(零扩展) - ❌ 不完全准确。 目标不是单纯增加位数,核心目标:扩展之后,数值大小保持不变,增加位数只是操作形式。
一句话总结: 符号扩展 / 零扩展:把短字长转为长字长,保证真值不变。 如果只是单纯加长位数但数值变了,这个扩展就是失败的。