整数的表示
上一节把四种编码都摆了出来,本节只做一件事:把它落到真实机器上。答案非常简洁——现代机器里的有符号整数一律用补码,无符号整数就是纯二进制,原码和反码只活在习题里。
本节真正的重点不是”用什么码”,而是由此导出的一个反直觉事实:有符号数和无符号数在硬件里是同一串位,甚至用的是同一个加法器。“有没有符号”不是数据的属性,是解释的属性。
机制
无符号整数:整个字长都是数值位
无符号整数没有符号位,unsigned 系列。硬件上它就是一个模
有符号整数:补码,且只有补码
选补码的理由已在 2.1.2 给全:减法消失、零唯一、符号位可参与运算。
408 的默认假设:一切有符号整数按补码存储、按补码运算。 题目若要考原码或反码,必然会明说。
同一串位的两种解释
这是本节最该记牢的一张图:同一个
flowchart TD B["位串 <b>1111 1111</b>"]:::bits U["按<b>无符号</b>解释<br/>= 255"]:::u S["按<b>补码</b>解释<br/>= −1"]:::s D["两者相差 <b>2⁸ = 256</b>"]:::note H["硬件里<b>只有这一串位</b><br/>加法器对两者<b>完全相同</b>"]:::hw B --> U B --> S U --> D S --> D B --> H classDef bits fill:#dbeafe,stroke:#2563eb,color:#1e3a5f classDef u fill:#fef3c7,stroke:#d97706,color:#78350f classDef s fill:#dcfce7,stroke:#16a34a,color:#14532d classDef note fill:#f1f5f9,stroke:#94a3b8,color:#334155 classDef hw fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
“加法器对两者完全相同”这句话不是简化说法,而是字面事实。 由 模运算,无符号加法和补码加法在模
那么硬件靠什么区分有符号与无符号
既然加法器不区分,区别必然出现在别的地方。一共五处,这五处正是所有”有符号/无符号”考题的出处:
| 区分点 | 无符号 | 有符号(补码) |
|---|---|---|
| 溢出判据 | 看进位标志 CF | 看溢出标志 OF |
| 大小比较 | 直接比位串 | 必须先看最高位 |
| 右移 | 逻辑右移(补 0) | 算术右移(补符号位) |
| 除法 / 取模 | 无符号除法指令 | 有符号除法指令 |
| 位数扩展 | 零扩展 | 符号扩展 |
一次加法之后,CF 和 OF 是同时被置位的,机器并不知道该看哪一个——决定看哪一个的是程序(编译器根据变量类型选择跳转指令)。溢出判据在 2.2.3 展开,移位在 2.2.2,扩展在 2.1.4。
这正是”C 的有符号溢出是未定义行为、无符号溢出有定义”的机器层根源:位串行为是一样的,但语言只承诺了其中一种解释。
常用字长与范围
408 的默认机器模型(也是 x86-64 上 C 的实际情况,long 除外):
| C 类型 | 位数 | 有符号范围 | 无符号范围 |
|---|---|---|---|
char | 8 | ||
short | 16 | ||
int | 32 | ||
long long | 64 |
数量级要有感觉:
long 的宽度依平台而变(Linux/macOS 64 位上是 8 字节,Windows 上是 4 字节),题目不给宽度就不要假设。
特征位串
考试里反复出现的几个,直接背下来比现场算快:
| 位串(32 位) | 有符号 | 无符号 | 身份 |
|---|---|---|---|
0x00000000 | 0 | 0 | 零 |
0x7FFFFFFF | INT_MAX | ||
0x80000000 | INT_MIN;仅最高位为 1 | ||
0xFFFFFFFF | 全 1;UINT_MAX | ||
0xFFFFFFFE |
“全 1 就是
边界
非对称性
补码的正负两侧不对称:
由此产生一串在真实代码里会出问题的等式:
前两个从 “连符号位取反加一” 直接可验证:1000…0 取反是 0111…1,加 1 又回到 1000…0。第三个在 x86 上会触发除法异常而不是给出错误值——这是少数几个”溢出会真的中断程序”的情形,见 2.2.4。
回绕不是溢出,溢出不是错误
三个词经常被混着用,在 408 里必须分开:
| 词 | 含义 | 是不是错误 |
|---|---|---|
| 回绕 | 结果对 | 不是,无符号运算的定义行为 |
| 溢出 | 数学结果超出该编码的表示范围 | 是”超范围”这一事实,由标志位报告 |
| 未定义行为 | C 语言层面对有符号溢出不作任何承诺 | 语言概念,不是硬件概念 |
硬件永远在做模
无符号数没有负数,但会变成很大的正数
一个在真实代码里出现频率极高的坑:
unsigned int n = 3;
for (unsigned int i = n; i >= 0; i--) { /* 死循环 */ }i >= 0 对无符号数恒为真,i 减到 0 后再减变成 0xFFFFFFFF,循环永不结束。根源就是无符号是环、不是数轴。
同类的还有 sizeof 的返回类型是无符号的 size_t,所以 if (a - b < 0) 在 a、b 都是 size_t 时永远不成立。相关的类型转换规则在 2.1.4。
整数在内存里的字节顺序是另一个问题
本节只说”一个整数由哪些位组成”,没有说这些字节按什么顺序放进内存。那是 2.3.4 大小端的内容,两者互不决定:大小端只影响多字节对象在内存中的排列,不改变寄存器里的数值。
对照速查
| 判据 | 结论 |
|---|---|
| 有符号整数用什么编码 | 补码(408 默认) |
| 加法器区分有无符号吗 | 不区分,位串完全相同 |
| 靠什么区分 | 溢出判据 / 比较 / 右移 / 除法 / 扩展 五处 |
| 全 1 位串是多少 | 有符号 |
0x80000000 是多少 | 有符号 |
考点
- 有符号数与无符号数在机器里是同一串位,共用一个加法器
- 区分只发生在溢出判据、比较、右移、除法、扩展五处
- 全 1 =
,与字长无关; 0x80000000有符号是 , 溢出 - 无符号数
i >= 0恒真,无符号是环不是数轴 - 回绕、溢出、未定义行为是三个不同层面的概念
long的宽度依平台而变,题目不给就不假设
链接
- 🏠 返回总览:计算机组成原理第 2 章:数据的表示和运算总览
- ⬅️ 上一节:2.1.2 定点数的编码表示
- ➡️ 下一节:2.1.4 C 语言中的整数类型及类型转换
- 📖 名词库:第 2 章名词库