《深入理解计算机系统(原书第3版)》第2章:信息的表示和处理 📚
!note 阅读范围
本笔记依据本地附件《深入理解计算机系统(原书第3版)》的第2章(PDF 第57---176页)整理,用自己的话讲解。书中涉及的具体数值、公式和推导均来自原书材料,未在材料中确认的细节标注为"待核对"。
!tip 一句话抓住本章计算机里的一切------数字、文字、代码------归根结底都是"0 和 1 的排列"。本章教你三种最重要的排列规则:无符号整数 (只能表示非负数)、补码 (能表示负数,C 语言的
int就是它)、IEEE 754 浮点数 (近似表示实数,C 语言的float和double就是它),以及当你用有限位数做算术时会发生什么诡异后果(比如正数加正数居然变成负数)。
1 本章学习目标 🎯
学完本章应该掌握:
- 十六进制、二进制、十进制的任意转换,这是阅读内存 dump、汇编输出的基本功。
- 大端法和小端法:同一个数字在不同机器上,字节排列顺序可能完全相反。
- 补码 :C 语言中
int的实际存储方式,为什么int的负数范围比正数多 1。 - 有符号数和无符号数混用的陷阱 :
-1 < 0U居然是假?这是无数 bug 的根源。 - 整数运算溢出的后果:正数加正数变成负数、乘法导致安全漏洞。
- IEEE 754 浮点数 :
float和double的内部构造(符号位 + 阶码 + 尾数),为什么0.1 + 0.2 != 0.3。 - 浮点运算不可结合 :
(a + b) + c不等于a + (b + c)。
为什么要学这一章?没有它,你看不懂内存中存储的数据、说不清一个 C 语言程序的比较运算为什么"反直觉"、也无法理解 XDR 安全漏洞(整数溢出导致只分配了 4096 字节而不是 4GB)或是爱国者导弹拦截失败(浮点精度丢失导致 28 名士兵死亡)。
它与后面的关系:第 3 章讲机器级编程,你需要能读懂寄存器里的十六进制数、理解补码的位操作指令;第 5 章讲优化,你需要知道编译器如何用移位替代乘除法;第 9 章讲虚拟内存,你需要知道每个字节都有一个虚拟地址。
!tip 建议学习路线
- 先理解"位"和"字节"是什么,学会十六进制 ↔ 二进制 ↔ 十进制的互转。
- 掌握大端/小端,知道同一个数字在不同机器上的存储差异。
- 深入理解无符号数和补码的编码公式,从位模式推算出数值。
- 理解有符号和无符号混用时的隐式转换规则(C 语言的"潜规则")。
- 掌握整数加减乘除溢出时的行为及其检测方法。
- 最后攻克浮点数:IEEE 754 的三段式编码,为什么浮点运算不精确。
2 整章知识地图 🗺️
#mermaid-svg-O6zgy4U5hdqu7L6g{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-O6zgy4U5hdqu7L6g .error-icon{fill:#552222;}#mermaid-svg-O6zgy4U5hdqu7L6g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-O6zgy4U5hdqu7L6g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-O6zgy4U5hdqu7L6g .marker.cross{stroke:#333333;}#mermaid-svg-O6zgy4U5hdqu7L6g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-O6zgy4U5hdqu7L6g p{margin:0;}#mermaid-svg-O6zgy4U5hdqu7L6g .edge{stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .section--1 rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section--1 path,#mermaid-svg-O6zgy4U5hdqu7L6g .section--1 circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section--1 polygon,#mermaid-svg-O6zgy4U5hdqu7L6g .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section--1 text{fill:#ffffff;}#mermaid-svg-O6zgy4U5hdqu7L6g .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-depth--1{stroke-width:17;}#mermaid-svg-O6zgy4U5hdqu7L6g .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled circle,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:lightgray;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:#efefef;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-0 rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section-0 path,#mermaid-svg-O6zgy4U5hdqu7L6g .section-0 circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section-0 polygon,#mermaid-svg-O6zgy4U5hdqu7L6g .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section-0 text{fill:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .node-icon-0{font-size:40px;color:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-depth-0{stroke-width:14;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled circle,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:lightgray;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:#efefef;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-1 rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section-1 path,#mermaid-svg-O6zgy4U5hdqu7L6g .section-1 circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section-1 polygon,#mermaid-svg-O6zgy4U5hdqu7L6g .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section-1 text{fill:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .node-icon-1{font-size:40px;color:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-depth-1{stroke-width:11;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled circle,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:lightgray;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:#efefef;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-2 rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section-2 path,#mermaid-svg-O6zgy4U5hdqu7L6g .section-2 circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section-2 polygon,#mermaid-svg-O6zgy4U5hdqu7L6g .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section-2 text{fill:#ffffff;}#mermaid-svg-O6zgy4U5hdqu7L6g .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-depth-2{stroke-width:8;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled circle,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:lightgray;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:#efefef;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-3 rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section-3 path,#mermaid-svg-O6zgy4U5hdqu7L6g .section-3 circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section-3 polygon,#mermaid-svg-O6zgy4U5hdqu7L6g .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section-3 text{fill:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .node-icon-3{font-size:40px;color:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-depth-3{stroke-width:5;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled circle,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:lightgray;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:#efefef;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-4 rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section-4 path,#mermaid-svg-O6zgy4U5hdqu7L6g .section-4 circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section-4 polygon,#mermaid-svg-O6zgy4U5hdqu7L6g .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section-4 text{fill:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .node-icon-4{font-size:40px;color:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-depth-4{stroke-width:2;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled circle,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:lightgray;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:#efefef;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-5 rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section-5 path,#mermaid-svg-O6zgy4U5hdqu7L6g .section-5 circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section-5 polygon,#mermaid-svg-O6zgy4U5hdqu7L6g .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section-5 text{fill:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .node-icon-5{font-size:40px;color:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-depth-5{stroke-width:-1;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled circle,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:lightgray;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:#efefef;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-6 rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section-6 path,#mermaid-svg-O6zgy4U5hdqu7L6g .section-6 circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section-6 polygon,#mermaid-svg-O6zgy4U5hdqu7L6g .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section-6 text{fill:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .node-icon-6{font-size:40px;color:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-depth-6{stroke-width:-4;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled circle,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:lightgray;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:#efefef;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-7 rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section-7 path,#mermaid-svg-O6zgy4U5hdqu7L6g .section-7 circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section-7 polygon,#mermaid-svg-O6zgy4U5hdqu7L6g .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section-7 text{fill:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .node-icon-7{font-size:40px;color:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-depth-7{stroke-width:-7;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled circle,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:lightgray;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:#efefef;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-8 rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section-8 path,#mermaid-svg-O6zgy4U5hdqu7L6g .section-8 circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section-8 polygon,#mermaid-svg-O6zgy4U5hdqu7L6g .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section-8 text{fill:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .node-icon-8{font-size:40px;color:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-depth-8{stroke-width:-10;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled circle,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:lightgray;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:#efefef;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-9 rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section-9 path,#mermaid-svg-O6zgy4U5hdqu7L6g .section-9 circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section-9 polygon,#mermaid-svg-O6zgy4U5hdqu7L6g .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section-9 text{fill:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .node-icon-9{font-size:40px;color:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-depth-9{stroke-width:-13;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled circle,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:lightgray;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:#efefef;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-10 rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section-10 path,#mermaid-svg-O6zgy4U5hdqu7L6g .section-10 circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section-10 polygon,#mermaid-svg-O6zgy4U5hdqu7L6g .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section-10 text{fill:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .node-icon-10{font-size:40px;color:black;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .edge-depth-10{stroke-width:-16;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled circle,#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:lightgray;}#mermaid-svg-O6zgy4U5hdqu7L6g .disabled text{fill:#efefef;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-root rect,#mermaid-svg-O6zgy4U5hdqu7L6g .section-root path,#mermaid-svg-O6zgy4U5hdqu7L6g .section-root circle,#mermaid-svg-O6zgy4U5hdqu7L6g .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-O6zgy4U5hdqu7L6g .section-root text{fill:#ffffff;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-root span{color:#ffffff;}#mermaid-svg-O6zgy4U5hdqu7L6g .section-2 span{color:#ffffff;}#mermaid-svg-O6zgy4U5hdqu7L6g .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-O6zgy4U5hdqu7L6g .edge{fill:none;}#mermaid-svg-O6zgy4U5hdqu7L6g .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-O6zgy4U5hdqu7L6g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 第2章 信息的表示和处理
信息存储基础
位 bit
字节 byte
十六进制 hex
字长 word size
字节顺序 大端小端
布尔代数和位运算
整数表示
无符号数 unsigned
补码 twos-complement
有符号 无符号 转换
扩展和截断
整数运算
无符号加法 模运算
补码加法 正负溢出
乘法 和 除法
乘以常数 移位优化
浮点数
二进制小数
IEEE 754 格式
规格化 非规格化 特殊值
舍入 rounding
浮点运算 不具结合性
逐个解释:
- 信息存储基础:这是本章的"语言基础"。位(bit)是最小存储单位,8 个位组成一个字节(byte)。十六进制是二进制的一种"速记法"。字长决定了指针能访问多少内存(32 位→4GB,64 位→16EB)。字节顺序决定了多字节数据在内存中怎么排列(大端vs小端)。布尔代数和位运算是"直接操控二进制位"的工具。
- 整数表示:无符号数就是纯二进制数;补码则用最高位表示负数(最高位是 1 就是负数)。它们可以互相转换(位模式不变、解释不同),这是 C 语言中无数 bug 的根源。
- 整数运算:因为位数有限,加法会溢出(结果超出表示范围)。乘法更容易溢出,但编译器会用移位+加减法来优化。除以 2 的幂就是用右移来实现。
- 浮点数 :用
V = (-1)^s × M × 2^E的方式近似表示实数。规格化数按 IEEE 754 标准编码,非规格化数用来表示极小的数(逐渐下溢),特殊值表示无穷大(±∞)和 NaN。
3 核心知识点逐个讲 👩🏫
3.1 知识点 1:位、字节与十六进制
3.1.1 ① 什么是它(一句话)
位 (bit)是计算机存储信息的最小单位,只有 0 或 1 两种状态。字节 (byte)是 8 个位的组合,是大多数计算机寻址的最小单位。十六进制是二进制的"速记法",用 1 位十六进制对应 4 位二进制。
3.1.2 ② 大白话解释
想象一个巨大的储物柜,每个格子只能放一个小球,小球要么是黑的(0),要么是白的(1)。每个格子就是一个位 。8 个格子排成一排就是一个字节。
问题是:如果你要记录一个格子里是 10110111,光念出来就要半天。十六进制就是一道"翻译压缩"------把每 4 个格子(位)翻译成一个符号:
| 4位二进制 | 十六进制 |
|---|---|
| 0000 | 0 |
| 0001 | 1 |
| ... | ... |
| 1001 | 9 |
| 1010 | A |
| 1011 | B |
| 1100 | C |
| 1101 | D |
| 1110 | E |
| 1111 | F |
这样 10110111 就变成了 B7------从 8 个字符压缩到 2 个字符。在 C 语言中,0xB7 或 0XB7 表示十六进制常量。
| 生活例子 | 对应关系 | 它帮你理解什么 | 类比的边界 |
|---|---|---|---|
| 储物柜格子 | 格子 → 位(0或1),8个格子一排 → 字节 | 位是最小单位,字节是基本封装 | 真实格子物理上不会"溢出" |
| 车牌号 | 每4位二进制 → 1位十六进制,就像"京A·12345"用6个字符表示完整信息 | 十六进制是二进制的缩写 | 车牌号不是纯数字编码 |
| 色卡编号 | 十六进制就是给每个4位模式起个名字(0~F),就像给每种颜色起个名字 | 记法转换,不改变"本质" | 颜色命名有主观性,十六进制编码是确定的 |
3.1.3 ③ 为什么会有它
- 如果没有位:无法表示--计算机底层电路只能识别高电压/低电压两种状态,所以"二进制"是唯一选择。
- 如果没有字节:每次都要单独处理 1 个位,内存管理和数据传输效率极低。8 位一组刚好能编码 256 种不同值,可以放一个英文字母(ASCII)、一个颜色分量或一个较小的整数。
- 如果没有十六进制 :二进制
1111001010110110110011读起来像天书,写错一个位都很难发现。十六进制把"阅读长度"缩小 4 倍,极大降低人工阅读和调试的出错率。
3.1.4 ④ 工作流程 / 演进过程
#mermaid-svg-kt9kRx9RirbbCIaU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kt9kRx9RirbbCIaU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kt9kRx9RirbbCIaU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kt9kRx9RirbbCIaU .error-icon{fill:#552222;}#mermaid-svg-kt9kRx9RirbbCIaU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kt9kRx9RirbbCIaU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kt9kRx9RirbbCIaU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kt9kRx9RirbbCIaU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kt9kRx9RirbbCIaU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kt9kRx9RirbbCIaU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kt9kRx9RirbbCIaU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kt9kRx9RirbbCIaU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kt9kRx9RirbbCIaU .marker.cross{stroke:#333333;}#mermaid-svg-kt9kRx9RirbbCIaU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kt9kRx9RirbbCIaU p{margin:0;}#mermaid-svg-kt9kRx9RirbbCIaU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kt9kRx9RirbbCIaU .cluster-label text{fill:#333;}#mermaid-svg-kt9kRx9RirbbCIaU .cluster-label span{color:#333;}#mermaid-svg-kt9kRx9RirbbCIaU .cluster-label span p{background-color:transparent;}#mermaid-svg-kt9kRx9RirbbCIaU .label text,#mermaid-svg-kt9kRx9RirbbCIaU span{fill:#333;color:#333;}#mermaid-svg-kt9kRx9RirbbCIaU .node rect,#mermaid-svg-kt9kRx9RirbbCIaU .node circle,#mermaid-svg-kt9kRx9RirbbCIaU .node ellipse,#mermaid-svg-kt9kRx9RirbbCIaU .node polygon,#mermaid-svg-kt9kRx9RirbbCIaU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kt9kRx9RirbbCIaU .rough-node .label text,#mermaid-svg-kt9kRx9RirbbCIaU .node .label text,#mermaid-svg-kt9kRx9RirbbCIaU .image-shape .label,#mermaid-svg-kt9kRx9RirbbCIaU .icon-shape .label{text-anchor:middle;}#mermaid-svg-kt9kRx9RirbbCIaU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kt9kRx9RirbbCIaU .rough-node .label,#mermaid-svg-kt9kRx9RirbbCIaU .node .label,#mermaid-svg-kt9kRx9RirbbCIaU .image-shape .label,#mermaid-svg-kt9kRx9RirbbCIaU .icon-shape .label{text-align:center;}#mermaid-svg-kt9kRx9RirbbCIaU .node.clickable{cursor:pointer;}#mermaid-svg-kt9kRx9RirbbCIaU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kt9kRx9RirbbCIaU .arrowheadPath{fill:#333333;}#mermaid-svg-kt9kRx9RirbbCIaU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kt9kRx9RirbbCIaU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kt9kRx9RirbbCIaU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kt9kRx9RirbbCIaU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kt9kRx9RirbbCIaU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kt9kRx9RirbbCIaU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kt9kRx9RirbbCIaU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kt9kRx9RirbbCIaU .cluster text{fill:#333;}#mermaid-svg-kt9kRx9RirbbCIaU .cluster span{color:#333;}#mermaid-svg-kt9kRx9RirbbCIaU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kt9kRx9RirbbCIaU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kt9kRx9RirbbCIaU rect.text{fill:none;stroke-width:0;}#mermaid-svg-kt9kRx9RirbbCIaU .icon-shape,#mermaid-svg-kt9kRx9RirbbCIaU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kt9kRx9RirbbCIaU .icon-shape p,#mermaid-svg-kt9kRx9RirbbCIaU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kt9kRx9RirbbCIaU .icon-shape .label rect,#mermaid-svg-kt9kRx9RirbbCIaU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kt9kRx9RirbbCIaU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kt9kRx9RirbbCIaU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kt9kRx9RirbbCIaU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
二进制位模式
每4位一组
查表映射到 0-F
十六进制表示
需要还原?
在代码/调试器中使用
逐步说明:
- 拿到的是一串 0 和 1(比如
1100100101111011)。 - 从右往左每 4 位分一组,最左边不够 4 位的补 0:
1100 1001 0111 1011。 - 每组查表:
1100→C、1001→9、0111→7、1011→B,得到0xC97B。 - 反过来,
0x39A7F8展开:3→0011、9→1001、A→1010、7→0111、F→1111、8→1000,得到二进制。
!example 🎬 动画脑补
把"十六进制转换"想象成一个翻译官。二进制是"位语言"------每个位自己在喊"我是0还是1"。翻译官把它们每 4 个编成一组,然后从兜里掏出一本"4位→符号"对照表,念出对应的符号(0~F)。反向翻译时,翻译官听到一个符号,就从表里翻出 4 个位的值,依次写下来。
3.1.5 ⑤ 图片说明(必须)
建议插入的图片
- 十六进制与二进制/十进制的三栏对照表(0~F)
- 一个字节(8位)拆分为两个十六进制数字的示意图
- 2 的幂次在十进制、二进制、十六进制三种表示法下的对照表
为什么看这张图 :能一眼看出"一个十六进制数字精确对应 4 个二进制位"这个核心关系,以及 2^10=1024(约 1K)、2^20≈1M 这种常用的对应。
可用于后续绘图的描述:一张横向表格,列出 2^0 到 2^15 的十进制值、二进制表示(1 后面跟 n 个 0)、十六进制表示(当 n=4k+i 时,开头为 1/2/4/8,后面跟 k 个 0)。
3.1.6 ⑥ 容易混淆的概念
| 对比维度 | 十六进制 | 十进制 | 二进制 |
|---|---|---|---|
| 是什么 | 以16为基数的记数法,用0-9和A-F | 以10为基数的记数法,日常使用 | 以2为基数的记数法,只用0和1 |
| 解决什么问题 | 给二进制做"速记",方便人阅读 | 符合人类10根手指的习惯 | 符合电路二值状态的物理实现 |
| 在计算机中怎么用 | 调试器、内存dump、汇编代码中表示地址和数据 | 高层应用中显示给人看的数值 | 机器的"母语",一切数据的最终形态 |
| 一个例子 | 0x7AF = 1967(十进制) |
1967(千位1+百位9+十位6+个位7) |
0111 1010 1111 |
3.1.7 ⑦ 面试会怎么问 / 怎么向别人讲
问题 :给你一个十六进制数 0xD5E4C,请转换为二进制。
回答 :把每个十六进制数字分别展开成 4 位二进制:D=1101、5=0101、E=1110、4=0100、C=1100,拼接得到 1101 0101 1110 0100 1100。反过来也一样,把二进制每 4 位一组映射到十六进制即可。
3.1.8 ⑧ 一句话总结
!note
十六进制是二进制的"压缩表示法"------每 1 位十六进制精确对应 4 位二进制,是人阅读机器位模式的桥梁。
3.1.9 知识点 1 自测 🧠
3.1.9.1 基础题(5 题)
- 1 个字节有多少位?
- 十六进制数字
F对应的十进制值和二进制值分别是什么? - 将
0x39A7F8转换为二进制。 - 将二进制
1100 1001 0111 1011转换为十六进制。 - C 语言中十六进制常数以什么前缀开头?
3.1.9.2 理解题(5 题)
- 为什么十六进制比十进制更适合作为二进制的"速记法"?
- 为什么计算机使用二进制而不是十进制来存储信息?
0x7AF对应的十进制值是多少?写出计算过程。- 2^10 和 2^20 的十六进制表示分别是什么?
- 为什么 1 个字节的值域是
0x00到0xFF?
3.1.9.3 思考题(3 题)
- 如果有一种新计算机使用三进制(每位可以是 0、1、2),1 个"三进制字节"(比如 6 个三进制位)能表示多少种不同的值?
- 不使用计算器,如何快速判断
0x1A3F和6719(十进制)哪个更大? - 为什么
2^n转换成十六进制特别简单?总结规律。
3.1.9.4 面试/表达题(5 题)
- 请口头完成
0x3F7到二进制的转换,并解释规则。 - 请口头完成二进制
1011 0110到十六进制的转换。 - C 语言中
0x1F和31和037(八进制)表示的是同一个值吗?为什么? - 一个 32 位的 IP 地址(如
192.168.1.1)如何在内存中用十六进制表示? - 为什么调试器中内存内容通常以十六进制显示而不是十进制?
点击查看答案
基础题
- 8 位。
- F = 15(十进制)= 1111(二进制)。
0x39A7F8=0011 1001 1010 0111 1111 1000。1100 1001 0111 1011=0xC97B。0x或0X。
理解题
- 因为 16 = 2^4,每 4 位二进制恰好对应 1 位十六进制,是齐整的映射关系。而 10 不是 2 的幂,十进制和二进制之间没有简单的分组对应。
- 因为物理电路最可靠地区分两种状态(高电压/低电压、有磁/无磁),而区分 10 种状态的电路要复杂得多且更容易出错。
0x7AF= 7×256 + 10×16 + 15×1 = 1792 + 160 + 15 = 1967。- 2^10 = 1024 =
0x400(1 后面跟 10 个 0 的二进制 → 2 个十六进制 0 不够 4 位补齐 →0x400);2^20 =0x100000。 - 1 字节 = 8 位,最小值
0000 0000=0x00,最大值1111 1111=0xFF(=255)。
思考题
- 6 个三进制位 = 3^6 = 729 种不同的值。
0x1A3F= 1×4096 + 10×256 + 3×16 + 15 = 4096 + 2560 + 48 + 15 = 6719,两者相等。- 2^n:当 n = 4k+i (0≤i<4) 时,二进制是 1 后面跟 n 个 0,十六进制开头为 1(i=0)、2(i=1)、4(i=2)、8(i=3),后面跟 k 个 0。
面试/表达题
3→0011、F→1111、7→0111,拼接为0011 1111 0111。规则:每个十六进制数字用 4 位二进制替换。1011→B、0110→6,结果为0xB6。- 是同一个值(都是 31)。
0x表示十六进制,0开头表示八进制(037= 3×8+7 = 31)。但注意:八进制在现代 C 编程中应尽量避免使用。 192.168.1.1→0xC0A80101(每个十进制段转为十六进制:192=C0, 168=A8, 1=01, 1=01)。- 因为十六进制和二进制之间的转换是 N:1 映射(4位↔1位),一眼就能看出位模式;而十进制和二进制之间没有简单对应关系。
3.2 知识点 2:字长(Word Size)与数据大小
3.2.1 ① 什么是它(一句话)
字长(word size)是计算机的"标称尺寸"------它决定了指针的大小,从而决定了虚拟地址空间的最大范围(32 位→4GB,64 位→16EB)。
3.2.2 ② 大白话解释
计算机就像一个城市,内存就是城市里的房子。每个房子都有一个门牌号(地址)。字长决定了门牌号最长能是多少位------32 位机器上,门牌号从 0 到 4,294,967,295(约第 43 亿号),所以最多能访问 4GB 内存。64 位机器上门牌号能到 16EB(1EB = 10 亿 GB),大到几乎用不完。
但"字长"不止影响地址。C 语言的不同数据类型在不同字长下的字节数也不一样:
| C 声明 | 32 位程序 | 64 位程序 |
|---|---|---|
char |
1 字节 | 1 字节 |
short |
2 字节 | 2 字节 |
int |
4 字节 | 4 字节 |
long |
4 字节 | 8 字节 |
char *(指针) |
4 字节 | 8 字节 |
float |
4 字节 | 4 字节 |
double |
8 字节 | 8 字节 |
long 和指针的大小会随字长变化------这就是为什么在 32 位机器上"能跑"的代码,移植到 64 位可能崩。
| 生活例子 | 对应关系 | 它帮你理解什么 | 类比的边界 |
|---|---|---|---|
| 城市门牌号 | 门牌号位数 → 字长,门牌号 → 地址,房屋 → 字节 | 字长决定地址空间上限 | 真实城市不可能有 16EB 个房子 |
| 快递单号 | 快递单号的长度决定了最多能有多少个快递 | 字长决定了地址范围 | 快递单号不是二进制 |
| 电话号码位数 | 7 位号码 → 最多 1000 万个号码,8 位 → 最多 1 亿个 | 字长翻倍,地址空间指数增长 | 电话号码不是 2 的幂次递增 |
3.2.3 ③ 为什么会有它
- 如果没有字长概念:程序无法知道指针占几个字节,也就无法正确访问内存。编译器需要知道"指针多宽"才能生成正确的机器码。
- 从 32 位迁移到 64 位的原因:4GB 已经不够用了。现代浏览器、大型游戏、数据库都需要远超 4GB 的内存。64 位一下子把天花板从 4GB 提到了 16EB。
- 向后兼容 :64 位机器能跑 32 位程序(用
gcc -m32编译),这让过渡期变得平滑。但这意味着要同时理解两种字长下的程序行为。
3.2.4 ④ 工作流程 / 演进过程
#mermaid-svg-5rrVQmeWCCRK118H{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5rrVQmeWCCRK118H .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5rrVQmeWCCRK118H .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5rrVQmeWCCRK118H .error-icon{fill:#552222;}#mermaid-svg-5rrVQmeWCCRK118H .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5rrVQmeWCCRK118H .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5rrVQmeWCCRK118H .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5rrVQmeWCCRK118H .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5rrVQmeWCCRK118H .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5rrVQmeWCCRK118H .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5rrVQmeWCCRK118H .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5rrVQmeWCCRK118H .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5rrVQmeWCCRK118H .marker.cross{stroke:#333333;}#mermaid-svg-5rrVQmeWCCRK118H svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5rrVQmeWCCRK118H p{margin:0;}#mermaid-svg-5rrVQmeWCCRK118H .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5rrVQmeWCCRK118H .cluster-label text{fill:#333;}#mermaid-svg-5rrVQmeWCCRK118H .cluster-label span{color:#333;}#mermaid-svg-5rrVQmeWCCRK118H .cluster-label span p{background-color:transparent;}#mermaid-svg-5rrVQmeWCCRK118H .label text,#mermaid-svg-5rrVQmeWCCRK118H span{fill:#333;color:#333;}#mermaid-svg-5rrVQmeWCCRK118H .node rect,#mermaid-svg-5rrVQmeWCCRK118H .node circle,#mermaid-svg-5rrVQmeWCCRK118H .node ellipse,#mermaid-svg-5rrVQmeWCCRK118H .node polygon,#mermaid-svg-5rrVQmeWCCRK118H .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5rrVQmeWCCRK118H .rough-node .label text,#mermaid-svg-5rrVQmeWCCRK118H .node .label text,#mermaid-svg-5rrVQmeWCCRK118H .image-shape .label,#mermaid-svg-5rrVQmeWCCRK118H .icon-shape .label{text-anchor:middle;}#mermaid-svg-5rrVQmeWCCRK118H .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5rrVQmeWCCRK118H .rough-node .label,#mermaid-svg-5rrVQmeWCCRK118H .node .label,#mermaid-svg-5rrVQmeWCCRK118H .image-shape .label,#mermaid-svg-5rrVQmeWCCRK118H .icon-shape .label{text-align:center;}#mermaid-svg-5rrVQmeWCCRK118H .node.clickable{cursor:pointer;}#mermaid-svg-5rrVQmeWCCRK118H .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5rrVQmeWCCRK118H .arrowheadPath{fill:#333333;}#mermaid-svg-5rrVQmeWCCRK118H .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5rrVQmeWCCRK118H .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5rrVQmeWCCRK118H .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5rrVQmeWCCRK118H .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5rrVQmeWCCRK118H .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5rrVQmeWCCRK118H .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5rrVQmeWCCRK118H .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5rrVQmeWCCRK118H .cluster text{fill:#333;}#mermaid-svg-5rrVQmeWCCRK118H .cluster span{color:#333;}#mermaid-svg-5rrVQmeWCCRK118H div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5rrVQmeWCCRK118H .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5rrVQmeWCCRK118H rect.text{fill:none;stroke-width:0;}#mermaid-svg-5rrVQmeWCCRK118H .icon-shape,#mermaid-svg-5rrVQmeWCCRK118H .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5rrVQmeWCCRK118H .icon-shape p,#mermaid-svg-5rrVQmeWCCRK118H .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5rrVQmeWCCRK118H .icon-shape .label rect,#mermaid-svg-5rrVQmeWCCRK118H .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5rrVQmeWCCRK118H .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5rrVQmeWCCRK118H .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5rrVQmeWCCRK118H :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} m32
m64
编译器决定目标字长
编译选项
32位程序: 指针=4字节, long=4字节
64位程序: 指针=8字节, long=8字节
最大地址空间 4GB
最大地址空间 16EB
3.2.5 ⑤ 图片说明
建议插入的图片
- 32 位 vs 64 位程序的地址空间对比图(4GB vs 16EB)
- 各 C 数据类型在 32 位和 64 位下的字节大小对比表
3.2.6 ⑥ 容易混淆的概念
| 对比维度 | 32 位程序 | 64 位程序 | 最关键区别 |
|---|---|---|---|
| 是什么 | 用 -m32 编译,可在 32 位或 64 位机器上运行 |
用 -m64 编译,只能在 64 位机器上运行 |
编译选项决定,不是机器决定 |
| 指针大小 | 4 字节 | 8 字节 | 影响所有指针运算 |
long 大小 |
4 字节 | 8 字节 | long 和 int 在 32 位下等价,64 位下不等 |
| 地址空间 | 最多 4GB | 最多 16EB | 64 位突破了 4GB 瓶颈 |
3.2.7 ⑦ 面试会怎么问
问题 :int 在 32 位和 64 位下分别是几个字节?long 呢?
回答 :int 在两种情况下通常都是 4 字节。long 在 32 位下是 4 字节,在 64 位下是 8 字节。这是最常见的移植问题------在 32 位下用 long 存指针没问题,但搬到 64 位后 long 变成了 8 字节,可能导致意外行为。如果要精确控制大小,请用 int32_t 和 int64_t。
3.2.8 ⑧ 一句话总结
!note
字长决定了指针有多大、能访问多少内存。
int32_t和int64_t等定长类型让程序员可以摆脱"典型值"的依赖,写出可移植代码。
3.2.9 知识点 2 自测 🧠
3.2.9.1 基础题(5 题)
- 32 位程序的最大虚拟地址空间是多少?
- C 语言中
char数据类型占几个字节? int32_t占几个字节?(提示:看名字)- 64 位程序中,指针变量占几个字节?
- C 语言中声明一个无符号的 32 位整数应使用什么类型名?
3.2.9.2 理解题(5 题)
- 为什么 32 位程序的地址空间上限是 4GB?
- 为什么
int在 64 位机器上通常还是 4 字节,而不是 8 字节? - 如果一个 32 位程序中用
int存了一个指针值,移植到 64 位后会发生什么? unsigned long和uint64_t有什么区别?- 大多数 64 位机器能运行 32 位程序,这种能力叫什么?
3.2.9.3 思考题(3 题)
- 如果未来出现 128 位字长的机器,理论上能访问多少内存?
- 为什么
float和double的字节数不随字长改变? sizeof(char)在 C 语言标准中始终等于 1,这意味着什么?
3.2.9.4 面试/表达题(5 题)
- 解释 32 位和 64 位程序的主要区别及迁移中常见的坑。
size_t是什么?它在 32 位和 64 位下分别是几个字节?- 你如何确保一个整型变量在 32 位和 64 位下都有相同的大小?
- 为什么 GCC 编译选项
-m32能让 64 位机器产生 32 位代码? - 解释
long在 32 位 Linux、64 位 Linux 和 64 位 Windows 上的大小差异。
点击查看答案
基础题
- 4GB(2^32 字节)。
- 1 字节。
- 4 字节(32 位 ÷ 8 位/字节 = 4 字节)。
- 8 字节(64 位 ÷ 8 = 8 字节)。
uint32_t。
理解题
- 每个地址是 32 位,最多有 2^32 个不同地址,每个地址指向 1 字节,所以最多 2^32 字节 = 4GB。
- 历史兼容性------大量现存代码假设
int是 4 字节。把int改为 8 字节会破坏大量程序。所以让long变长来提供 8 字节整数。 - 在 32 位下,指针 4 字节,
int也是 4 字节,可以互转。64 位下指针变成 8 字节,但int仍是 4 字节,存指针值会截断高 4 字节,导致野指针/崩溃。 unsigned long的大小取决于平台(32 位→4 字节,64 位 Linux→8 字节,64 位 Windows→4 字节)。uint64_t明确保证 8 字节,跨平台一致。- 向后兼容(backward compatibility)。
思考题
- 2^128 字节 ≈ 3.4×10^38 字节 ≈ 340 亿亿亿亿 GB,远超可观测宇宙的原子总数。
float和double的大小是由 IEEE 754 标准规定的(4 字节和 8 字节),与机器字长无关。它们有自己的精度/范围设计,不需要随字长扩展。sizeof的单位是"char 的大小",所以sizeof(char)始终为 1。这意味着sizeof返回的是"需要多少个 char 才能存下该类型"。
面试/表达题
- 主要区别:指针和
long的大小。常见坑:用int存指针值、sizeof(long) != sizeof(int)的假设、malloc参数的类型size_t大小变化。 size_t是sizeof返回值的类型,无符号。32 位→4 字节,64 位→8 字节。- 使用
int32_t、int64_t、uint32_t、uint64_t等<stdint.h>中定义的定长类型。 - 编译器后端支持生成 32 位指令、使用 32 位 ABI(应用程序二进制接口),让 64 位 CPU 以兼容模式运行 32 位代码。
- 32 位 Linux:4 字节。64 位 Linux(LP64 模型):8 字节。64 位 Windows(LLP64 模型):4 字节。这是一个经典的跨平台差异。
3.3 知识点 3:字节顺序------大端法与小端法
3.3.1 ① 什么是它(一句话)
大端法 (big-endian)把最高有效字节存在最小地址;小端法(little-endian)把最低有效字节存在最小地址。同一个多字节数字在不同机器上,内存排列可能"颠倒"。
3.3.2 ② 大白话解释
假设你有一个 4 字节的整数 0x12345678(十六进制),要存到内存地址 0x100 到 0x103 这四个字节中。
大端法 认为"最重要的应该排在最前面"------最高字节 0x12 存在地址 0x100,然后是 0x34、0x56、0x78。读出来就是 12 34 56 78,跟写出来的顺序一模一样。
小端法 认为"最小的先来"------最低字节 0x78 存在地址 0x100,然后依次 0x56、0x34、0x12。读出来是 78 56 34 12,跟书写顺序完全颠倒。
| 地址 | 大端法 | 小端法 |
|---|---|---|
| 0x100 | 12 | 78 |
| 0x101 | 34 | 56 |
| 0x102 | 56 | 34 |
| 0x103 | 78 | 12 |
大多数 Intel 兼容机使用小端法(所以你的 PC 很可能就是小端法)。网络传输通常使用大端法("网络字节序")。
| 生活例子 | 对应关系 | 它帮你理解什么 | 类比的边界 |
|---|---|---|---|
| 读数字的习惯 | 中文"一百二十三"先说百位(大端);英文"one hundred twenty-three"也是大端。但念阿拉伯数字时我们从左(高位)往右(低位)读 | 大端像我们写数字的习惯,先写最高位 | 人类习惯大端,但机器的"自然实现"往往偏小端 |
| 字母排列 | 大端像"ABCD",小端像"DCBA"存到相同的格子里 | 同样四个字母,排列方式可以完全不同 | 字母排序有语义,字节排序纯粹是约定 |
| 快递包裹编号 | 大端:省份→城市→区→街道(粗到细)。小端:街道→区→城市→省份 | 两种"分层"方式各有道理 | 包裹编号有层次语义,字节只是一个平的数字 |
3.3.3 ③ 为什么会有它
- 为什么会有两种:不是谁对谁错,而是不同设计者选了不同的"方便"。小端法在硬件实现上有优势------做加法时先处理低位、进位往高位传,跟"小端先到"的顺序一致。大端法对人阅读更友好------内存dump从左往右看过去,数字也是从左往右的。
- 如果没有统一约定 :两台不同字节序的机器通过网络传数据时,接收方会把
0x12345678读成0x78563412。这就是为什么网络协议(如 TCP/IP)定义了"网络字节序"(大端),发送前必须转换。
3.3.4 ④ 工作流程 / 演进过程
#mermaid-svg-0tdsAWSWSqmlwH2F{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0tdsAWSWSqmlwH2F .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0tdsAWSWSqmlwH2F .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0tdsAWSWSqmlwH2F .error-icon{fill:#552222;}#mermaid-svg-0tdsAWSWSqmlwH2F .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0tdsAWSWSqmlwH2F .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0tdsAWSWSqmlwH2F .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0tdsAWSWSqmlwH2F .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0tdsAWSWSqmlwH2F .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0tdsAWSWSqmlwH2F .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0tdsAWSWSqmlwH2F .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0tdsAWSWSqmlwH2F .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0tdsAWSWSqmlwH2F .marker.cross{stroke:#333333;}#mermaid-svg-0tdsAWSWSqmlwH2F svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0tdsAWSWSqmlwH2F p{margin:0;}#mermaid-svg-0tdsAWSWSqmlwH2F .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0tdsAWSWSqmlwH2F .cluster-label text{fill:#333;}#mermaid-svg-0tdsAWSWSqmlwH2F .cluster-label span{color:#333;}#mermaid-svg-0tdsAWSWSqmlwH2F .cluster-label span p{background-color:transparent;}#mermaid-svg-0tdsAWSWSqmlwH2F .label text,#mermaid-svg-0tdsAWSWSqmlwH2F span{fill:#333;color:#333;}#mermaid-svg-0tdsAWSWSqmlwH2F .node rect,#mermaid-svg-0tdsAWSWSqmlwH2F .node circle,#mermaid-svg-0tdsAWSWSqmlwH2F .node ellipse,#mermaid-svg-0tdsAWSWSqmlwH2F .node polygon,#mermaid-svg-0tdsAWSWSqmlwH2F .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0tdsAWSWSqmlwH2F .rough-node .label text,#mermaid-svg-0tdsAWSWSqmlwH2F .node .label text,#mermaid-svg-0tdsAWSWSqmlwH2F .image-shape .label,#mermaid-svg-0tdsAWSWSqmlwH2F .icon-shape .label{text-anchor:middle;}#mermaid-svg-0tdsAWSWSqmlwH2F .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0tdsAWSWSqmlwH2F .rough-node .label,#mermaid-svg-0tdsAWSWSqmlwH2F .node .label,#mermaid-svg-0tdsAWSWSqmlwH2F .image-shape .label,#mermaid-svg-0tdsAWSWSqmlwH2F .icon-shape .label{text-align:center;}#mermaid-svg-0tdsAWSWSqmlwH2F .node.clickable{cursor:pointer;}#mermaid-svg-0tdsAWSWSqmlwH2F .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0tdsAWSWSqmlwH2F .arrowheadPath{fill:#333333;}#mermaid-svg-0tdsAWSWSqmlwH2F .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0tdsAWSWSqmlwH2F .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0tdsAWSWSqmlwH2F .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0tdsAWSWSqmlwH2F .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0tdsAWSWSqmlwH2F .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0tdsAWSWSqmlwH2F .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0tdsAWSWSqmlwH2F .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0tdsAWSWSqmlwH2F .cluster text{fill:#333;}#mermaid-svg-0tdsAWSWSqmlwH2F .cluster span{color:#333;}#mermaid-svg-0tdsAWSWSqmlwH2F div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0tdsAWSWSqmlwH2F .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0tdsAWSWSqmlwH2F rect.text{fill:none;stroke-width:0;}#mermaid-svg-0tdsAWSWSqmlwH2F .icon-shape,#mermaid-svg-0tdsAWSWSqmlwH2F .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0tdsAWSWSqmlwH2F .icon-shape p,#mermaid-svg-0tdsAWSWSqmlwH2F .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0tdsAWSWSqmlwH2F .icon-shape .label rect,#mermaid-svg-0tdsAWSWSqmlwH2F .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0tdsAWSWSqmlwH2F .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0tdsAWSWSqmlwH2F .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0tdsAWSWSqmlwH2F :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 大端法
小端法
整数 0x12345678
存到内存
地址0x100: 12
地址0x101: 34
地址0x102: 56
地址0x103: 78
地址0x100: 78
地址0x101: 56
地址0x102: 34
地址0x103: 12
网络传输前
需转为大端
Intel/AMD
默认存储
!example 🎬 动画脑补
有两个人排队进电梯。电梯有四层(四个地址格子),每层只能站一个人(一个字节)。
大端法:让"地位最高"的队长
12先进入一层,然后是34、56、78。小端法:让"年纪最小"的队员
78先进入一层,然后是56、34、12。电梯到了对方面前,如果对方不知道是用哪种方式排的队,就会把队长认成队员、队员认成队长。
3.3.5 ⑤ 图片说明
建议插入的图片
- 大端法和小端法的对比图:同一个 4 字节整数,两种排列方式的地址→值对应关系
show_bytes程序的输出示例:在大端和小端机器上打印同一个整数的字节序列
为什么看这张图:直观看到"同一数字在不同机器上字节排列完全颠倒",这是网络编程和二进制文件格式必须面对的问题。
3.3.6 ⑥ 容易混淆的概念
| 对比维度 | 大端法 | 小端法 | 最关键区别 |
|---|---|---|---|
| 是什么 | 最高有效字节存在最小地址 | 最低有效字节存在最小地址 | "谁先被存"的规则相反 |
| 常见平台 | Sun SPARC、PowerPC、网络字节序 | Intel x86/x64、ARM(可配置) | PC 几乎都是小端 |
| 阅读友好性 | 内存 dump 和书写顺序一致 | 内存 dump 和书写顺序颠倒 | 大端对人友好 |
| 硬件实现 | 需额外处理 | 低位先到,和进位方向一致 | 小端对硬件友好 |
3.3.7 ⑦ 面试会怎么问
问题:如何用 C 代码判断当前机器是大端法还是小端法?
回答 :定义一个 int x = 1,然后用 char *p = (char *)&x 取第一个字节。如果 *p == 1 则是小端法(低位字节 0x01 在低地址),否则是大端法。这在家庭作业 2.58 中有详细讨论。
3.3.8 ⑧ 一句话总结
!note
大端法"高位放低地址",小端法"低位放低地址"。你的 PC 极有可能是小端法,但网络通信必须注意字节序转换。
3.3.9 知识点 3 自测 🧠
3.3.9.1 基础题(5 题)
- 大端法的"大"指的是什么?
0x12345678在小端法机器上,最低地址的字节(第一个字节)的值是多少?- C 语言中的
show_bytes函数的作用是什么? - "网络字节序"使用的是大端法还是小端法?
- 同一个数字,在大端法和小端法机器上,位模式(bit pattern)是否相同?
3.3.9.2 理解题(5 题)
- 为什么说"阅读大端法机器的内存 dump 更方便"?
- 用
show_bytes打印字符串"12345",在大端和小端机器上结果一样吗?为什么? - 为什么 C 语言的
union或指针强制类型转换可以用来检测字节序? 0x87654321的低 2 字节在小端法、大端法下,show_bytes分别输出什么?- 两台不同字节序的机器通过 Socket 传
int数据时会发生什么?如何避免?
3.3.9.3 思考题(3 题)
- 如果一个处理器支持运行时切换大端/小端模式,什么时候会用到这种能力?
- 为什么 ARM 处理器被设计成可以配置字节序而 x86 是固定的小端?
- 单字节数据(如
char、字符串)有字节序问题吗?为什么?
3.3.9.4 面试/表达题(5 题)
- 口头解释大端法和小端法的区别,并给出一个具体例子。
- 你在
gdb中看到了内存78 56 34 12,原始 32 位整数在小端和大端机器上分别是多少? - 什么是"字节序",为什么它只在多字节数据类型时有意义?
- C 语言读写二进制文件时,如何保证跨平台的可移植性?
htons()、htonl()、ntohs()、ntohl()这些函数的作用和命名含义?
点击查看答案
基础题
- "大"指最高有效字节(most significant byte,MSB)存放在最低地址,即"大"的先来。
0x78(最低有效字节在最低地址)。- 以字节为单位打印任意程序对象的字节序列,用于观察底层位模式。
- 大端法。
- 位模式(所有位)相同,但位的空间分布(字节排列顺序)不同。位模式在 CPU 寄存器中一样,但在内存中字节排列不同。
理解题
- 内存地址从左到右递增,大端法把高位字节放左边,跟人书写数字的习惯一致,所以
12 34 56 78直接就是0x12345678。 - 一样。字符串每个字符就是一个字节(
'1'=0x31,'2'=0x32......),单字节没有字节序问题。而且 ASCII 码对所有系统通用。 - 联合体(union)让同一内存区域可以按不同方式解释;指针强制转换
(char *) &x让程序按字节访问多字节数据的第一个字节,从而判断低位是否存在低地址。 - 小端法:
21 43(低位在前);大端法:87 65(高位在前)。 - 接收方会把
0x12345678读成0x78563412。解决:发送方用htonl()转为网络字节序(大端),接收方用ntohl()转回本机字节序。
思考题
- 仿真不同架构的程序、运行虚拟机时模拟目标平台的字节序、调试网络协议时切换字节序以测试代码的健壮性。
- x86 从 8086 时代就确定了小端,几十年累积的软件生态不可能再改。ARM 作为后来者,为了在嵌入式/网络设备中灵活应用,设计成了可配置的(但 Android/iOS 实际都用小端模式)。
- 没有。单字节数据只有一个字节,不存在"先存哪个字节"的问题。字符串实际上是字符数组,每个字符是一个字节,所以也没有字节序问题------这也是文本数据比二进制数据更具平台独立性的根本原因。
面试/表达题
- 大端:
0x12345678存为12 34 56 78(高位在低地址);小端:0x12345678存为78 56 34 12(低位在低地址)。Intel/AMD 是小端,网络传输是大端。 - 小端→
0x12345678(低地址0x78);大端→0x78563412(低地址0x78是最低字节)。注意:需要知道数据的原始值才能确定哪个是正确的。 - 字节序是"多字节数据内部各字节在内存中的排列顺序"。单字节没有内部排列一说。
- 写入前将多字节整数转为固定字节序(通常网络字节序),读取时按同样规则转回。或者使用文本格式替代二进制格式。
hton= host to network(本机→网络),ntoh= network to host(网络→本机),s= short(16 位),l= long(32 位)。它们将 16 位/32 位整数在本机字节序和网络字节序(大端)之间转换。
3.4 知识点 4:布尔代数与 C 语言位运算
3.4.1 ① 什么是它(一句话)
布尔代数 是用 1(真)和 0(假)进行的逻辑运算。C 语言位级运算 (&、|、~、^)直接在二进制位的层面进行操作,是底层编程最基础的工具。
3.4.2 ② 大白话解释
想象每个位是一个小开关(ON=1,OFF=0)。四种基本操作:
&(AND,与) :"两个开关都 ON 才 ON"。0&0=0,0&1=0,1&0=0,1&1=1。常用于"掩码"------用一个全是 1 的模式把想保留的位"筛出来"。|(OR,或):"任意一个 ON 就 ON"。常用于"置位"------把指定的几个位强行设为 1。~(NOT,取反) :"ON 变 OFF,OFF 变 ON"。~0=1,~1=0。^(XOR,异或) :"两个不同才 ON"。0^0=0,0^1=1,1^0=1,1^1=0。妙用:a^a=0,所以可以用异或实现不借助临时变量的交换。
| 生活例子 | 对应关系 | 它帮你理解什么 | 类比的边界 |
|---|---|---|---|
| 两道门的房间 | AND:两道门都锁着才能进 → 两个位都是 1 结果才 1 | 与运算的要求"两个都满足" | 门锁有无穷多种,位只有两种状态 |
| 两条路的交叉口 | OR:任意一条路可通行就能到达 → 任意一个位是 1 结果就是 1 | 或运算的"任一满足即可" | 路口通行不是二值的 |
| 开关+反相器 | NOT:开关按下→灯灭,松开→灯亮 | 取反操作翻转状态 | 实际电路有延迟 |
3.4.3 ③ 为什么会有它
- 如果没有位运算 :无法直接操控硬件寄存器(比如设置 GPIO 引脚)、无法实现高效的位图(bitmap)、无法做权限掩码(如 Linux 文件权限
rwx用 3 位表示)。 - 为什么用位向量表示集合 :一个 w 位的位向量可以表示
{0, 1, ..., w-1}的任意子集。a & b就是交集,a | b就是并集,~a就是补集。这在信号掩码、权限系统中大量使用。 - 为什么编译器喜欢位运算:位运算是最快的 CPU 指令之一(通常 1 个时钟周期),乘法可能需要 3 个以上。
3.4.4 ④ 工作流程 / 演进过程
#mermaid-svg-RrGhdwszxJebwD4P{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RrGhdwszxJebwD4P .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RrGhdwszxJebwD4P .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RrGhdwszxJebwD4P .error-icon{fill:#552222;}#mermaid-svg-RrGhdwszxJebwD4P .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RrGhdwszxJebwD4P .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RrGhdwszxJebwD4P .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RrGhdwszxJebwD4P .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RrGhdwszxJebwD4P .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RrGhdwszxJebwD4P .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RrGhdwszxJebwD4P .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RrGhdwszxJebwD4P .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RrGhdwszxJebwD4P .marker.cross{stroke:#333333;}#mermaid-svg-RrGhdwszxJebwD4P svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RrGhdwszxJebwD4P p{margin:0;}#mermaid-svg-RrGhdwszxJebwD4P .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RrGhdwszxJebwD4P .cluster-label text{fill:#333;}#mermaid-svg-RrGhdwszxJebwD4P .cluster-label span{color:#333;}#mermaid-svg-RrGhdwszxJebwD4P .cluster-label span p{background-color:transparent;}#mermaid-svg-RrGhdwszxJebwD4P .label text,#mermaid-svg-RrGhdwszxJebwD4P span{fill:#333;color:#333;}#mermaid-svg-RrGhdwszxJebwD4P .node rect,#mermaid-svg-RrGhdwszxJebwD4P .node circle,#mermaid-svg-RrGhdwszxJebwD4P .node ellipse,#mermaid-svg-RrGhdwszxJebwD4P .node polygon,#mermaid-svg-RrGhdwszxJebwD4P .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RrGhdwszxJebwD4P .rough-node .label text,#mermaid-svg-RrGhdwszxJebwD4P .node .label text,#mermaid-svg-RrGhdwszxJebwD4P .image-shape .label,#mermaid-svg-RrGhdwszxJebwD4P .icon-shape .label{text-anchor:middle;}#mermaid-svg-RrGhdwszxJebwD4P .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RrGhdwszxJebwD4P .rough-node .label,#mermaid-svg-RrGhdwszxJebwD4P .node .label,#mermaid-svg-RrGhdwszxJebwD4P .image-shape .label,#mermaid-svg-RrGhdwszxJebwD4P .icon-shape .label{text-align:center;}#mermaid-svg-RrGhdwszxJebwD4P .node.clickable{cursor:pointer;}#mermaid-svg-RrGhdwszxJebwD4P .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RrGhdwszxJebwD4P .arrowheadPath{fill:#333333;}#mermaid-svg-RrGhdwszxJebwD4P .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RrGhdwszxJebwD4P .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RrGhdwszxJebwD4P .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RrGhdwszxJebwD4P .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RrGhdwszxJebwD4P .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RrGhdwszxJebwD4P .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RrGhdwszxJebwD4P .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RrGhdwszxJebwD4P .cluster text{fill:#333;}#mermaid-svg-RrGhdwszxJebwD4P .cluster span{color:#333;}#mermaid-svg-RrGhdwszxJebwD4P div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RrGhdwszxJebwD4P .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RrGhdwszxJebwD4P rect.text{fill:none;stroke-width:0;}#mermaid-svg-RrGhdwszxJebwD4P .icon-shape,#mermaid-svg-RrGhdwszxJebwD4P .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RrGhdwszxJebwD4P .icon-shape p,#mermaid-svg-RrGhdwszxJebwD4P .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RrGhdwszxJebwD4P .icon-shape .label rect,#mermaid-svg-RrGhdwszxJebwD4P .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RrGhdwszxJebwD4P .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RrGhdwszxJebwD4P .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RrGhdwszxJebwD4P :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 两个操作数
按位执行运算
每位独立计算
组合成结果
以 0x69 & 0x55 为例:
0x69=0110 10010x55=0101 0101- 按位 AND:
0100 0001=0x41
!example 🎬 动画脑补
两队人面对面站着,每个人手里举着一个牌子(0 或 1)。AND 运算就是"两人都举 1 的,记录为 1;否则记录为 0"。OR 运算就是"至少一人举 1 的,记录为 1"。XOR 就是"两人举的不一样,记录为 1"。每对人独立计算,最后得到的牌子序列就是结果。
3.4.5 ⑤ 图片说明
建议插入的图片
- 四种布尔运算的真值表(AND、OR、NOT、XOR)
- 两个位向量做位运算的并行示意图
- 掩码运算示例图:原始数据、掩码、结果三层对比
3.4.6 ⑥ 容易混淆的概念
| 对比维度 | 位级运算 & | | 逻辑运算 && || | 最关键区别 |
| --- | --- | --- | --- |
| 是什么 | 对每一位做布尔运算 | 对整个值的"真/假"做判断 | 粒度不同:按位 vs 整体 |
| 返回值 | 位模式(多位的值) | 只能是 1 或 0 | 位运算返回多位数,逻辑运算返回 0 或 1 |
| 短路求值 | 不短路,两边都算 | && 和 || 有短路:如果左边已能确定结果,右边不执行 | 逻辑运算有短路特性 |
| 典型用法 | flags & MASK、x | (1<<3) | if (p && *p)、a>0 && b/a>1 | 位运算操作位,逻辑运算控制流程 |
3.4.7 ⑦ 面试会怎么问
问题 :0x69 && 0x55 的结果是什么?0x69 & 0x55 呢?
回答 :0x69 && 0x55 → 两个都是非零值,都视为 TRUE,TRUE && TRUE = TRUE,所以结果是 1(逻辑与)。0x69 & 0x55 → 01101001 & 01010101 = 01000001 = 0x41(按位与)。同样的符号用在不同上下文中,含义完全不同。
3.4.8 ⑧ 一句话总结
!note
&/|/~/^是直接在二进制位上的手术刀,&&/||/!是判断"整体是否为真"的开关。前者返回多位的值,后者只返回 0 或 1。
3.4.9 知识点 4 自测 🧠
3.4.9.1 基础题(5 题)
1 ^ 1的结果是多少?0x0F & 0xF0的结果是多少?~0在 8 位系统上的结果是多少?0x69 | 0x55的结果是多少?a ^ a对于任意 a 的结果是多少?
3.4.9.2 理解题(5 题)
- 为什么说
&适合做"掩码"操作?举例说明。 - 用异或运算交换两个变量的值,原理是什么?
- 逻辑与
&&的"短路求值"是什么意思?为什么这很重要? - 为什么位级运算比算术运算(加减乘除)更快?
- "位向量表示集合":用位向量
[01101001]表示集合{0, 3, 5, 6}的原理是什么?
3.4.9.3 思考题(3 题)
x & (x - 1)这个表达式有什么特殊用途?- 如何仅用位运算判断一个数是否是 2 的幂?
- 布尔代数中
&对|有分配律(a & (b | c) = (a & b) | (a & c)),但整数*对+也有分配律。为什么"布尔代数像整数代数"这件事很重要?
3.4.9.4 面试/表达题(5 题)
- 写一个宏
IS_POWER_OF_2(x),判断 x 是否是 2 的幂。 - 如何用位运算实现"将 x 的第 n 位设为 1"和"将 x 的第 n 位清零"?
- 用位向量表示 8 个信号,
0x3C表示哪些信号有效? - 在什么场景下
a & b和a && b的结果相同? x ^ y ^ y的结果是什么?解释原理和应用场景。
点击查看答案
基础题
0(异或:相同为 0)。0x00(00001111 & 11110000 = 00000000)。0xFF(00000000每位取反 →11111111)。0x7D(01101001 | 01010101 = 01111101)。0(任何数和自己异或都等于 0)。
理解题
- 掩码操作:用一个位模式"筛选"出想要的位。例如
x & 0xFF保留最低 8 位,其他位清零。0xFF就是掩码------需要保留的位置放 1,需要清零的位置放 0。 - 三步:
a = a ^ b,b = a ^ b(此时 b 变成原来的 a),a = a ^ b(此时 a 变成原来的 b)。原理是x ^ y ^ y = x,连续异或同一个值两次就"抵消"了。 - 如果
&&左边为假,右边根本不会执行。例如p != NULL && *p > 0,如果 p 是空指针,*p就不会执行,避免了空指针解引用崩溃。 - 位级运算在硬件上只需一层与/或/非门,一个时钟周期完成。加法需要进位链(多层门延迟),乘法需要多个加法器组合,除法更慢。
- 位向量的第 i 位(最低位是第 0 位)为 1,表示 i 属于集合。
[01101001]:第 0、3、5、6 位为 1,所以集合是 {0, 3, 5, 6}。
思考题
x & (x - 1)会清除 x 最低位的 1。用于统计二进制中有多少个 1(Brian Kernighan 算法)、判断是否为 2 的幂。(x != 0) && ((x & (x - 1)) == 0)。如果是 2 的幂,二进制中只有一个 1,减 1 后那个 1 变 0,后面全变 1,按位与结果必为 0。- 因为布尔代数和整数代数的结构相似,编译器可以利用同样的数学性质(交换律、结合律、分配律)对位运算表达式做优化重排。同时,这些性质让程序员可以用位运算替代某些算术运算。
面试/表达题
#define IS_POWER_OF_2(x) ((x) != 0 && ((x) & ((x) - 1)) == 0)- 设第 n 位为 1:
x | (1 << n)。将第 n 位清零:x & ~(1 << n)。翻转第 n 位:x ^ (1 << n)。 0x3C = 0011 1100,第 2、3、4、5 位为 1,表示信号 2、3、4、5 有效。- 当 a 和 b 都只能取 0 或 1 时,按位与和逻辑与的结果相同(两者都返回 0 或 1 中的一个)。
- 结果是
x。原理:y ^ y = 0,x ^ 0 = x。应用:加密(用密钥异或加密,再异或同一个密钥解密);RAID 奇偶校验;图形学中的异或绘制。
3.5 知识点 5:C 语言移位运算
3.5.1 ① 什么是它(一句话)
左移 (<<)将位模式整体左移,右边补 0。逻辑右移 (>>,无符号数)左边补 0。算术右移 (>>,有符号数)左边补符号位。移位量不能为负或超过字长。
3.5.2 ② 大白话解释
把二进制数字想象成一排人。左移就是让所有人同时向左走 k 步,最左边 k 个人掉出队列(丢弃),右边空出来的 k 个位置被"替补队员 0"补上。
右移有两种:
- 逻辑右移:所有人向右走,左边空出来的位置全部补 0。无符号数用这种方式。
- 算术右移:所有人向右走,左边空出来的位置补"原来的第一个人"(符号位)------如果原来是负数(最左边是 1),左边就补 1;如果是正数(最左边是 0),就补 0。这意味着负数右移后还是负数。
| 生活例子 | 对应关系 | 它帮你理解什么 | 类比的边界 |
|---|---|---|---|
| 排队的士兵 | 士兵 → 位,左移 → 全体向左走,掉出队列 → 丢弃高位 | 移位的"丢弃"和"补位" | 士兵不会因为移动而改变值 |
| 十进制小数点移动 | ×10 → 小数点右移一位(相当于十进制左移),÷10 → 小数点左移一位 | 左移 ≈ 乘以 2^k,右移 ≈ 除以 2^k | 十进制是 10 的幂,二进制是 2 的幂 |
| 滚动字幕 LED 屏 | 字符整体向左滚动,右边出现空白 | 左移后低位补 0 | LED 屏是循环的,移位通常不循环 |
3.5.3 ③ 为什么会有它
- 如果没有移位运算 :乘/除以 2 的幂必须用作真正的乘除法指令------乘法指令可能比移位慢 3~10 倍(在旧机器上差距更大)。编译器大量使用移位来优化
x * 8(变成x << 3)。 - 算术右移的意义 :保持负数符号位不变,这样
-8 >> 1得到-4,而不是一个很大的正数。如果右移时补 0,-8(二进制11111000)右移 1 位变成01111100(正数 124),完全错误。 - 为什么 C 标准不对有符号数右移做硬性规定:因为不同硬件实现可能不同。几乎所有现代机器都用算术右移,但标准为了兼容留了余地。
3.5.4 ④ 工作流程 / 演进过程
#mermaid-svg-znX5DGHK55r3XV3o{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-znX5DGHK55r3XV3o .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-znX5DGHK55r3XV3o .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-znX5DGHK55r3XV3o .error-icon{fill:#552222;}#mermaid-svg-znX5DGHK55r3XV3o .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-znX5DGHK55r3XV3o .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-znX5DGHK55r3XV3o .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-znX5DGHK55r3XV3o .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-znX5DGHK55r3XV3o .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-znX5DGHK55r3XV3o .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-znX5DGHK55r3XV3o .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-znX5DGHK55r3XV3o .marker{fill:#333333;stroke:#333333;}#mermaid-svg-znX5DGHK55r3XV3o .marker.cross{stroke:#333333;}#mermaid-svg-znX5DGHK55r3XV3o svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-znX5DGHK55r3XV3o p{margin:0;}#mermaid-svg-znX5DGHK55r3XV3o .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-znX5DGHK55r3XV3o .cluster-label text{fill:#333;}#mermaid-svg-znX5DGHK55r3XV3o .cluster-label span{color:#333;}#mermaid-svg-znX5DGHK55r3XV3o .cluster-label span p{background-color:transparent;}#mermaid-svg-znX5DGHK55r3XV3o .label text,#mermaid-svg-znX5DGHK55r3XV3o span{fill:#333;color:#333;}#mermaid-svg-znX5DGHK55r3XV3o .node rect,#mermaid-svg-znX5DGHK55r3XV3o .node circle,#mermaid-svg-znX5DGHK55r3XV3o .node ellipse,#mermaid-svg-znX5DGHK55r3XV3o .node polygon,#mermaid-svg-znX5DGHK55r3XV3o .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-znX5DGHK55r3XV3o .rough-node .label text,#mermaid-svg-znX5DGHK55r3XV3o .node .label text,#mermaid-svg-znX5DGHK55r3XV3o .image-shape .label,#mermaid-svg-znX5DGHK55r3XV3o .icon-shape .label{text-anchor:middle;}#mermaid-svg-znX5DGHK55r3XV3o .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-znX5DGHK55r3XV3o .rough-node .label,#mermaid-svg-znX5DGHK55r3XV3o .node .label,#mermaid-svg-znX5DGHK55r3XV3o .image-shape .label,#mermaid-svg-znX5DGHK55r3XV3o .icon-shape .label{text-align:center;}#mermaid-svg-znX5DGHK55r3XV3o .node.clickable{cursor:pointer;}#mermaid-svg-znX5DGHK55r3XV3o .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-znX5DGHK55r3XV3o .arrowheadPath{fill:#333333;}#mermaid-svg-znX5DGHK55r3XV3o .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-znX5DGHK55r3XV3o .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-znX5DGHK55r3XV3o .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-znX5DGHK55r3XV3o .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-znX5DGHK55r3XV3o .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-znX5DGHK55r3XV3o .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-znX5DGHK55r3XV3o .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-znX5DGHK55r3XV3o .cluster text{fill:#333;}#mermaid-svg-znX5DGHK55r3XV3o .cluster span{color:#333;}#mermaid-svg-znX5DGHK55r3XV3o div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-znX5DGHK55r3XV3o .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-znX5DGHK55r3XV3o rect.text{fill:none;stroke-width:0;}#mermaid-svg-znX5DGHK55r3XV3o .icon-shape,#mermaid-svg-znX5DGHK55r3XV3o .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-znX5DGHK55r3XV3o .icon-shape p,#mermaid-svg-znX5DGHK55r3XV3o .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-znX5DGHK55r3XV3o .icon-shape .label rect,#mermaid-svg-znX5DGHK55r3XV3o .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-znX5DGHK55r3XV3o .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-znX5DGHK55r3XV3o .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-znX5DGHK55r3XV3o :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 左移 <<
右移 >>
无符号数
有符号数
操作数 x 和移位量 k
移位方向?
整体左移k位
右边补k个0
高位丢弃
操作数类型?
逻辑右移
左边补0
算术右移
左边补符号位
!example 🎬 动画脑补
一列火车有 w 节车厢,每节装着一个 0 或 1。左移:所有车厢同时往前(高位方向)冲 k 格,最前面 k 节冲出了铁轨(丢弃),后面 k 节空位被"空车厢 0"补上。逻辑右移:所有车厢往后冲,前面空位用空车厢补上。算术右移:前后冲,但前面空位由车头(符号位)的"克隆车厢"来补------如果车头是 1,补的就是 1。
3.5.5 ⑤ 图片说明
建议插入的图片
- 左移、逻辑右移、算术右移三者的对比图(同一个位向量,三种操作的差异)
x << k = x * 2^k的直观演示:以十进制类比
3.5.6 ⑥ 容易混淆的概念
| 对比维度 | 逻辑右移 | 算术右移 | 最关键区别 |
|---|---|---|---|
| 是什么 | 右移后左边补 0 | 右移后左边补原来的符号位 | 补 0 vs 补符号位 |
| 适用类型 | 无符号数 | 有符号数(几乎所有的现代 C 编译器) | 由数据类型决定 |
| 对负数的影响 | 负数变成很大的正数(错!) | 负数保持为负,-8 >> 1 = -4 |
算术右移保持符号 |
| C 语言保证 | 无符号数右移是逻辑右移(保证) | 有符号数右移可能是算术也可能是逻辑(未保证,但实践上都是算术) | 有符号右移不可移植 |
3.5.7 ⑦ 面试会怎么问
问题 :-1 >> 1 在大多现代机器上结果是多少?
回答 :-1 的补码表示(8 位)是 11111111,算术右移 1 位,左边补符号位 1,得到 11111111(仍然是 -1)。所以 -1 >> 1 = -1,不是 0。
3.5.8 ⑧ 一句话总结
!note
左移 = 乘以 2^k,右移 = 除以 2^k。无符号数用逻辑右移(补 0),有符号数用算术右移(补符号位)。移位一定要确保移位量在合法范围内。
3.5.9 知识点 5 自测 🧠
3.5.9.1 基础题(5 题)
0x8 << 2的结果是多少?0x80 >> 3(无符号)的结果是多少?- 移位量 k 应该满足什么条件?
- 左移一位相当于乘以多少?
- 逻辑右移和算术右移的本质区别是什么?
3.5.9.2 理解题(5 题)
- 为什么
x << 1有时会比x * 2更快? - C 语言中
-16 >> 2的值是多少?为什么? - 如果 C 语言对
int用逻辑右移而不是算术右移,-16 >> 2的值会是多少(假设 8 位)? - 为什么 C 语言标准不要求有符号数右移必须是算术右移?
1 << 31在 32 位系统上得到什么值?它是有符号数的什么特殊值?
3.5.9.3 思考题(3 题)
- 移位操作能替代任意常数的乘法吗?例如
x * 7? - 如果移位量 k 等于或大于字长 w,会发生什么?(如
1 << 32在 32 位系统上) - 如何实现一个"循环左移"(rotating shift):左移出去的位补到右边?
3.5.9.4 面试/表达题(5 题)
- 解释左移、逻辑右移、算术右移的区别,各自适用什么情况。
(unsigned) -1 >> 1的值是多少?(int) ((unsigned) -1 >> 1)呢?- 什么是未定义行为(undefined behavior)?为什么移位操作中存在未定义行为?
- 用移位实现"求 x 的绝对值"(仅用位运算和加法)。
x << 3等价于x * 8,这个关系在补码下是否始终成立?是否可能溢出?
点击查看答案
基础题
0x20(0x8=0b1000,左移 2 位 →0b100000 = 0x20 = 32)。0x10(0x80=128,除以 8=16=0x10)。0 ≤ k < w(w 为字长),不能为负,不能等于或超过字长。- 2。
- 逻辑右移左边补 0,算术右移左边补符号位(保持正/负性不变)。
理解题
- 左移指令通常 1 个时钟周期,整数乘法可能需要 3 个或更多时钟周期。编译器会自动将
x*2优化为x<<1,但了解这一点有助于理解编译器行为。 -4。-16的 8 位补码是11110000,算术右移 2 位 →11111100=-4。11110000逻辑右移 2 位 →00111100=60,完全错误。- 为了兼容不同的硬件------有些早期机器可能只支持逻辑右移。不过几乎所有现代 CPU 都支持算术右移,主流编译器也都使用算术右移。
0x80000000,即INT_MIN(-2147483648)。在补码中,这是一个边界值,取负(-INT_MIN)会溢出。
思考题
- 可以,但需要组合。
x*7=(x<<3) - x=x*8 - x。编译器通常对于乘常数会尝试用移位和加减法组合替代。 - 在 C 语言中是未定义行为。在 x86 上,移位量会被截断(只取低 5 位),
1<<32等同于1<<0= 1。但绝不能依赖此行为。 - 循环左移:
(x << k) | (x >> (w - k))。高位被左移推出去的位,同时从右移进来(通过右移 w-k 位放到低位)。
面试/表达题
- 左移:整体左移,右边补 0,等价乘 2^k。逻辑右移:右边移出,左边补 0,无符号数使用。算术右移:右边移出,左边补符号位,有符号数使用(保持正负性)。
(unsigned) -1=UINT_MAX(全 1),右移 1 位(逻辑右移)→ 最高位变 0,得到0x7FFFFFFF(32 位下是 2147483647)。转回int后仍是正数 2147483647。- 未定义行为:C 标准不规定结果的动作。移位操作中包括:移位量为负数、移位量 ≥ 字长。这意味着不同编译器/平台可能有不同结果,程序不可移植。
int mask = x >> 31;(算术右移,正数得 0,负数得全 1);return (x + mask) ^ mask;(正数不变,负数 = ~x + 1 = -x)。- 补码和左移的关系等价于乘法(位级行为一致)。但是,当结果超出表示范围时,乘法会溢出,左移也会有同样的溢出行为。实际上编译器利用这一等价性来优化,因为溢出行为在位级上是一致的。
3.6 知识点 6:无符号数与补码编码
3.6.1 ① 什么是它(一句话)
无符号数 (unsigned)将位模式直接解释为非负整数。补码 (two's-complement)是 C 语言 int 的编码方式,最高有效位解释为负权(-2^(w-1)),其他位仍是正权。
3.6.2 ② 大白话解释
假设你有 4 个位。无符号数的解读方式:每个位都代表一个正的 2 的幂。1001 = 1×8 + 0×4 + 0×2 + 1×1 = 9。
补码的解读方式:最高位是"负的" 2 的幂,其余位正常。还是 1001,但现在:
- 最高位(最左边):负权 -1×8 = -8(因为它代表
-2^3) - 第三位:0×4 = 0
- 第二位:0×2 = 0
- 最低位:1×1 = 1
- 总和:-8 + 1 = -7
同一个位模式 1001,无符号解读是 9,补码解读是 -7。位模式一模一样,只是"解读规则"不同。
| 位模式(4位) | 无符号值 | 补码值 |
|---|---|---|
| 0000 | 0 | 0 |
| 0001 | 1 | 1 |
| 0111 | 7 | 7 |
| 1000 | 8 | -8 |
| 1111 | 15 | -1 |
补码的精妙之处:负数范围比正数多 1(4 位:-8 到 7),而且加法硬件完全不需要知道你在做有符号还是无符号------同一套电路对两种情况都正确。
| 生活例子 | 对应关系 | 它帮你理解什么 | 类比的边界 |
|---|---|---|---|
| 钟表 | 12 点=0 点。13 点=1 点(模 12)。补码的负数就像"逆时针转"。 | 模运算:转一圈回到原点 | 钟表模 12,补码模 2^w |
| 里程表(汽车) | 全 9 再转一格就变全 0。1111 加 1 变 0000(溢出) |
溢出就像里程表"转回去" | 里程表是十进制不是二进制 |
| 借钱和还钱(负债务) | 你有 0 元,借了 1 元 → 账面是"负 1",可以通过还 1 元清零。补码的 -1 就是 1111(全 1),加 1 变成 0000 |
补码的减法可以变成加法 | 债务有法律含义,补码只是约定 |
3.6.3 ③ 为什么会有它
- 如果没有补码 :表示负数需要额外的符号位和处理逻辑。反码(Ones' Complement)和原码(Sign-Magnitude)都有"+0 和 -0 两种零"的怪异问题。补码只有一个零(
0000),而且硬件可以用同一套加法器做有符号和无符号运算。 - 为什么负数范围比正数多 1 :因为 0 是"非负的",占了一个正半轴的位置。w 位补码的范围是
[-2^(w-1), 2^(w-1)-1],不对称。 - 为什么几乎所有现代机器都用补码:因为硬件实现最简单,加法和减法的电路无需区分有无符号。
3.6.4 ④ 工作流程 / 演进过程
#mermaid-svg-tKTMvZRKEG0QPC1Y{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tKTMvZRKEG0QPC1Y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tKTMvZRKEG0QPC1Y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tKTMvZRKEG0QPC1Y .error-icon{fill:#552222;}#mermaid-svg-tKTMvZRKEG0QPC1Y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tKTMvZRKEG0QPC1Y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tKTMvZRKEG0QPC1Y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tKTMvZRKEG0QPC1Y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tKTMvZRKEG0QPC1Y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tKTMvZRKEG0QPC1Y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tKTMvZRKEG0QPC1Y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tKTMvZRKEG0QPC1Y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tKTMvZRKEG0QPC1Y .marker.cross{stroke:#333333;}#mermaid-svg-tKTMvZRKEG0QPC1Y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tKTMvZRKEG0QPC1Y p{margin:0;}#mermaid-svg-tKTMvZRKEG0QPC1Y .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tKTMvZRKEG0QPC1Y .cluster-label text{fill:#333;}#mermaid-svg-tKTMvZRKEG0QPC1Y .cluster-label span{color:#333;}#mermaid-svg-tKTMvZRKEG0QPC1Y .cluster-label span p{background-color:transparent;}#mermaid-svg-tKTMvZRKEG0QPC1Y .label text,#mermaid-svg-tKTMvZRKEG0QPC1Y span{fill:#333;color:#333;}#mermaid-svg-tKTMvZRKEG0QPC1Y .node rect,#mermaid-svg-tKTMvZRKEG0QPC1Y .node circle,#mermaid-svg-tKTMvZRKEG0QPC1Y .node ellipse,#mermaid-svg-tKTMvZRKEG0QPC1Y .node polygon,#mermaid-svg-tKTMvZRKEG0QPC1Y .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tKTMvZRKEG0QPC1Y .rough-node .label text,#mermaid-svg-tKTMvZRKEG0QPC1Y .node .label text,#mermaid-svg-tKTMvZRKEG0QPC1Y .image-shape .label,#mermaid-svg-tKTMvZRKEG0QPC1Y .icon-shape .label{text-anchor:middle;}#mermaid-svg-tKTMvZRKEG0QPC1Y .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tKTMvZRKEG0QPC1Y .rough-node .label,#mermaid-svg-tKTMvZRKEG0QPC1Y .node .label,#mermaid-svg-tKTMvZRKEG0QPC1Y .image-shape .label,#mermaid-svg-tKTMvZRKEG0QPC1Y .icon-shape .label{text-align:center;}#mermaid-svg-tKTMvZRKEG0QPC1Y .node.clickable{cursor:pointer;}#mermaid-svg-tKTMvZRKEG0QPC1Y .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tKTMvZRKEG0QPC1Y .arrowheadPath{fill:#333333;}#mermaid-svg-tKTMvZRKEG0QPC1Y .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tKTMvZRKEG0QPC1Y .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tKTMvZRKEG0QPC1Y .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tKTMvZRKEG0QPC1Y .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tKTMvZRKEG0QPC1Y .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tKTMvZRKEG0QPC1Y .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tKTMvZRKEG0QPC1Y .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tKTMvZRKEG0QPC1Y .cluster text{fill:#333;}#mermaid-svg-tKTMvZRKEG0QPC1Y .cluster span{color:#333;}#mermaid-svg-tKTMvZRKEG0QPC1Y div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tKTMvZRKEG0QPC1Y .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tKTMvZRKEG0QPC1Y rect.text{fill:none;stroke-width:0;}#mermaid-svg-tKTMvZRKEG0QPC1Y .icon-shape,#mermaid-svg-tKTMvZRKEG0QPC1Y .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tKTMvZRKEG0QPC1Y .icon-shape p,#mermaid-svg-tKTMvZRKEG0QPC1Y .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tKTMvZRKEG0QPC1Y .icon-shape .label rect,#mermaid-svg-tKTMvZRKEG0QPC1Y .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tKTMvZRKEG0QPC1Y .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tKTMvZRKEG0QPC1Y .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tKTMvZRKEG0QPC1Y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 位模式 1100
无符号解释
补码解释
8 + 4 + 0 + 0 = 12
最高位-1×8=-8
- 1×4 + 0 + 0 = -4
3.6.5 ⑤ 图片说明
建议插入的图片
- 无符号数和补码的"权"对比图:同样 4 位,无符号每位都是正权,补码最高位是负权
- w=4 时所有 16 个位模式对应的无符号值和补码值对照表
- "补码轮"示意图:0 在正上方,顺时针加 1,逆时针减 1
3.6.6 ⑥ 容易混淆的概念
| 对比维度 | 无符号数 | 补码 | 最关键区别 |
|---|---|---|---|
| 是什么 | 每位都是正权,只能表示非负数 | 最高位是负权,能表示正/负数和零 | 最高位的"权重符号" |
| 范围(w=32) | 0, 4294967295 | -2147483648, 2147483647 | 范围形状完全不同 |
| 0 的表示 | 只有 000...0 | 只有 000...0(一个零) | 补码只有一个零(优势) |
| C 语言对应 | unsigned int |
int(默认) |
unsigned 关键字区分 |
3.6.7 ⑦ 面试会怎么问
问题 :int 类型的 TMin(最小负数)和 TMax(最大正数)分别是多少(32 位)?为什么 TMin 的绝对值比 TMax 的绝对值大 1?
回答 :TMin = -2^31 = -2147483648,TMax = 2^31 - 1 = 2147483647。不对称是因为 0 在补码中占了一个"非负"的位置(000...0),所以正数的最大值比负数的最负值少 1。这也是为什么 -INT_MIN 会溢出------INT_MIN 没有对应的正数。
3.6.8 ⑧ 一句话总结
!note
补码把最高位的 2^(w-1) 从正权改为负权,使得同一个位模式既能表示整数又能表示负数。关键是:位模式不变,解释规则决定数值。
3.6.9 知识点 6 自测 🧠
3.6.9.1 基础题(5 题)
- 4 位无符号数的最大值和最小值分别是多少?
- 4 位补码的范围是多少(最小到最大)?
- 位模式
1111(4 位)分别用无符号和补码解释,数值各是多少? - 补码中
-1的位模式是什么(假设 4 位)? - 补码的全称(英文)是什么?
3.6.9.2 理解题(5 题)
- 为什么补码只有一个"零"的表示,而反码有两个?
- 为什么说同一套硬件电路可以同时做无符号和补码加法?
- 为什么
int TMin的绝对值比TMax大 1? B2U_4([1000])的值是多少?B2T_4([1000])的值是多少?- 为什么补码的加法硬件可以跟无符号数共用?
3.6.9.3 思考题(3 题)
- 是否存在一个 C 表达式,其值同时等于
INT_MAX和UINT_MAX? - 设计一个"只有无符号数"的编程语言有什么优缺点?
- 如果让你设计一种新的整数编码,你会从哪里改进补码?
3.6.9.4 面试/表达题(5 题)
- 用公式写出无符号数编码函数
B2U_w(x)和补码编码函数B2T_w(x)。 (unsigned) -1的值是什么?这个转换发生了什么?- 解释"补码"名字的由来:为什么叫"two's complement"?
- Java 为什么不支持无符号整数类型?
- 如何求一个补码负数的绝对值?有什么陷阱?
点击查看答案
基础题
- 无符号:0 到 15(2^4 - 1)。补码:-8 到 7(-2^3 到 2^3 - 1)。
1111无符号值=15,补码值=-1。- 补码:
-1的 4 位表示是1111。 B2U_4([1000]) = 8,B2T_4([1000]) = -8。- Two's-complement。
理解题
- 补码中,最高位的权是
-2^(w-1),其余位正权。对于零:000...0,所有位为 0,值为 0。如果全 1:111...1=-2^(w-1) + (2^(w-1)-1) = -1,不是 0。反码中全 0 = +0,全 1 = -0,有两个零。 - 因为补码和无符号数加法在位级上完全一致------都是逐位相加,进位传播,溢出截断。唯一的区别在于"你怎么看这个结果"。
- w 位补码范围是
[-2^(w-1), 2^(w-1)-1]。0 占了非负数区域的一个位置(000...0),所以正数最大值少 1。|-2^(w-1)| = 2^(w-1),超出正数最大值2^(w-1)-1。 B2U_4([1000]) = 8,B2T_4([1000]) = -8。同一个位模式,解释不同。- 补码加法电路只做二进制加法+忽略最高位进位(截断),结果的位模式对两种解释都是正确的。计算时不需要知道数字是有符号还是无符号。
思考题
- C 语言中每个类型的范围是确定的,
INT_MAX = 2^31 - 1,UINT_MAX = 2^32 - 1,没有值能同时等于两者。但同一个位模式(如0xFFFFFFFF)被解释为无符号是UINT_MAX,解释为补码是-1。 - 优点:没有隐式类型转换的陷阱,行为更可预测。缺点:无法表示负数,很多算法(如差值、偏移)需要负数。
- 可考虑用额外的"溢出标记位"替代截断、用不同的偏置(如 BCD 编码)、或在硬件层面支持可选的高精度扩展。但补码的"硬件简单性"优势很难超越。
面试/表达题
B2U_w(x) = Σ(x_i × 2^i)i=0...w-1;B2T_w(x) = -x_(w-1) × 2^(w-1) + Σ(x_i × 2^i)i=0...w-2。(unsigned) -1 = UINT_MAX。转换过程中位模式不变(-1的补码是全 1),改变的是解释方式------从补码全 1(= -1)变为无符号全 1(= UINT_MAX)。这是 C 语言转换规则"位模式不变,数值可能改变"的经典例子。- "two's complement":对于非负数 x,
-x的 w 位补码表示通过计算x' = 2^w - x得到------2^w这个"2 的幂"参与取补,所以叫"two's"。反码("ones' complement")则是用(2^w - 1) - x计算,参与的是全 1 的模式。 - Java 的设计哲学是简洁和安全,他们认为无符号数带来的混淆(如 C 中的隐式转换陷阱)超过其收益。但 Java 8 之后也提供了
Integer.toUnsignedString()等方法处理无符号语义。 - 绝对值:正数不变,负数取
-x = ~x + 1。陷阱:INT_MIN(-2147483648)取绝对值会溢出-------INT_MIN = INT_MIN,因为INT_MIN在补码中没有对应的正数。
3.7 知识点 7:有符号数与无符号数的转换------隐式类型陷阱
3.7.1 ① 什么是它(一句话)
C 语言中,有符号数和无符号数之间的强制类型转换 遵循"位模式不变,数值可能改变 "的原则。当两者混用在同一个表达式中时,有符号数会被隐式转换为无符号数,这是无数 bug 的根源。
3.7.2 ② 大白话解释
想象两种"语言"解读同一个手势。手势本身(位模式)没变,但不同语言里这个手势的含义完全不一样。
C 语言的转换规则:T2U(x) = 如果 x<0 则 x+2^w,否则 x。U2T(x) = 如果 x>TMax_w 则 x-2^w,否则 x。
最经典的陷阱:-1 < 0U。因为 -1 是有符号数,0U 是无符号数,表达式中有无符号数时,有符号数被隐式转换为无符号数。(unsigned) -1 = UINT_MAX(约 43 亿),而 0U 是 0。所以 4294967295U < 0U 为假!这个表达式居然是 false。
| 生活例子 | 对应关系 | 它帮你理解什么 | 类比的边界 |
|---|---|---|---|
| 外国人看本地手势 | 同一个手势 → 同一个位模式;不同文化 → 不同解释 | 转换不改变位,改变意义 | 手势在不同文化中有不同含义,但至少都"有意义" |
| 温度计刻度 | 同一位置 → 同一读数;摄氏度 → 无符号(只能非负);华氏度 → 有符号(可能为负) | 相同刻度,不同解释 | 实际有绝对零度限制 |
| Excel 单元格格式 | 同一个数字,显示为"文本"vs"数字"vs"日期" | 底层值不变,显示/解读方式不同 | Excel 可能有四舍五入 |
3.7.3 ③ 为什么会有它
- 为什么不禁止混用:历史原因------C 语言从时代起就允许这种灵活性。禁止混用会破坏大量现有代码。
- 为什么隐式转换方向是"往无符号转":这是 C 语言标准的"通常算术转换"规则------当无符号和有符号类型同时出现时,两者都转为无符号。这个规则不是任何人主观设计的好方案,而是历史演进的结果。
3.7.4 ④ 关键转换公式
#mermaid-svg-g7QGwOjTAhCnH17y{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-g7QGwOjTAhCnH17y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-g7QGwOjTAhCnH17y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-g7QGwOjTAhCnH17y .error-icon{fill:#552222;}#mermaid-svg-g7QGwOjTAhCnH17y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-g7QGwOjTAhCnH17y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-g7QGwOjTAhCnH17y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-g7QGwOjTAhCnH17y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-g7QGwOjTAhCnH17y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-g7QGwOjTAhCnH17y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-g7QGwOjTAhCnH17y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-g7QGwOjTAhCnH17y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-g7QGwOjTAhCnH17y .marker.cross{stroke:#333333;}#mermaid-svg-g7QGwOjTAhCnH17y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-g7QGwOjTAhCnH17y p{margin:0;}#mermaid-svg-g7QGwOjTAhCnH17y .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-g7QGwOjTAhCnH17y .cluster-label text{fill:#333;}#mermaid-svg-g7QGwOjTAhCnH17y .cluster-label span{color:#333;}#mermaid-svg-g7QGwOjTAhCnH17y .cluster-label span p{background-color:transparent;}#mermaid-svg-g7QGwOjTAhCnH17y .label text,#mermaid-svg-g7QGwOjTAhCnH17y span{fill:#333;color:#333;}#mermaid-svg-g7QGwOjTAhCnH17y .node rect,#mermaid-svg-g7QGwOjTAhCnH17y .node circle,#mermaid-svg-g7QGwOjTAhCnH17y .node ellipse,#mermaid-svg-g7QGwOjTAhCnH17y .node polygon,#mermaid-svg-g7QGwOjTAhCnH17y .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-g7QGwOjTAhCnH17y .rough-node .label text,#mermaid-svg-g7QGwOjTAhCnH17y .node .label text,#mermaid-svg-g7QGwOjTAhCnH17y .image-shape .label,#mermaid-svg-g7QGwOjTAhCnH17y .icon-shape .label{text-anchor:middle;}#mermaid-svg-g7QGwOjTAhCnH17y .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-g7QGwOjTAhCnH17y .rough-node .label,#mermaid-svg-g7QGwOjTAhCnH17y .node .label,#mermaid-svg-g7QGwOjTAhCnH17y .image-shape .label,#mermaid-svg-g7QGwOjTAhCnH17y .icon-shape .label{text-align:center;}#mermaid-svg-g7QGwOjTAhCnH17y .node.clickable{cursor:pointer;}#mermaid-svg-g7QGwOjTAhCnH17y .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-g7QGwOjTAhCnH17y .arrowheadPath{fill:#333333;}#mermaid-svg-g7QGwOjTAhCnH17y .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-g7QGwOjTAhCnH17y .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-g7QGwOjTAhCnH17y .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g7QGwOjTAhCnH17y .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-g7QGwOjTAhCnH17y .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g7QGwOjTAhCnH17y .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-g7QGwOjTAhCnH17y .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-g7QGwOjTAhCnH17y .cluster text{fill:#333;}#mermaid-svg-g7QGwOjTAhCnH17y .cluster span{color:#333;}#mermaid-svg-g7QGwOjTAhCnH17y div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-g7QGwOjTAhCnH17y .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-g7QGwOjTAhCnH17y rect.text{fill:none;stroke-width:0;}#mermaid-svg-g7QGwOjTAhCnH17y .icon-shape,#mermaid-svg-g7QGwOjTAhCnH17y .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g7QGwOjTAhCnH17y .icon-shape p,#mermaid-svg-g7QGwOjTAhCnH17y .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-g7QGwOjTAhCnH17y .icon-shape .label rect,#mermaid-svg-g7QGwOjTAhCnH17y .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g7QGwOjTAhCnH17y .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-g7QGwOjTAhCnH17y .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-g7QGwOjTAhCnH17y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
有符号数 x
x < 0?
x + 2^w → 无符号数
x(不变)→ 无符号数
3.7.5 ⑤ 图片说明
建议插入的图片
- T2U 和 U2T 的"映射图":一个圆圈上标注补码值和无符号值的对应关系
- C 语言表达式
-1 < 0U的逐步求值过程图
3.7.6 ⑥ 容易混淆的概念
| 对比维度 | 显式转换 (unsigned) x |
隐式转换(表达式混用) | 最关键区别 |
|---|---|---|---|
| 是什么 | 程序员主动写的强制类型转换 | 编译器自动插入的转换 | 显式是主动的,隐式是"潜规则" |
| 何时发生 | 代码中写了 (unsigned) |
表达式中有无符号数和有符号数混用 | 隐式转换可能让新手完全不知情 |
| 风险 | 程序员知道自己在做什么 | 行为难以预料,容易引入 bug | 隐式转换是"暗箭" |
3.7.7 ⑦ 面试会怎么问
问题 :sizeof 返回的是无符号类型 size_t。解释为什么 sizeof(s) < strlen(s) - 1 可能导致死循环。
回答 :sizeof 返回 size_t(无符号),strlen 也返回 size_t。如果 s 是空字符串,strlen(s) = 0,strlen(s) - 1 = (size_t) -1 = SIZE_MAX(无符号下溢),这是一个巨大的正数。sizeof(s) 不可能大于 SIZE_MAX,所以比较为真,可能导致缓冲区越界或死循环。正确做法:先将长度转为有符号比较,或改用条件判断。
3.7.8 ⑧ 一句话总结
!note
有符号和无符号混用时,有符号会被隐式转为无符号。
-1 < 0U居然是假。避免混用,或者用gcc -Wsign-conversion让编译器帮你检查。
3.7.9 知识点 7 自测 🧠
3.7.9.1 基础题(5 题)
(unsigned) -1的值是什么(假设 32 位)?T2U_4(-8)的值是多少?U2T_4(15)的值是多少?- C 语言中,
int和unsigned混用时,哪个会隐式转换? size_t是有符号还是无符号类型?
3.7.9.2 理解题(5 题)
- 为什么 C 语言设计者选择"往无符号方向"隐式转换?
T2U_w(x) = x + 2^w (当 x < 0),为什么这样是对的?用二进制位模式解释。- 为什么
strlen(s) - 1对于空字符串是一个危险表达式? - Java 为什么不支持无符号整数?
- 补码和无符号数为什么可以进行相同的乘加运算?原理是什么?
3.7.9.3 思考题(3 题)
- 如果 C 语言不允许有符号和无符号隐式混用,会怎样?
- 如何检查一个无符号数转为有符号数是否会溢出?
- 设计一个安全的
subtract(a, b)函数,返回a - b,但不依赖有符号/无符号的隐式转换。
3.7.9.4 面试/表达题(5 题)
- 写出
T2U_w(x)和U2T_w(u)的公式并解释。 - 解释"C 语言的通常算术转换(usual arithmetic conversions)"。
- 在 32 位系统上,
-2147483648 < 2147483647U的结果是什么?为什么? - 给出一个实际代码示例,其中有符号/无符号的隐式转换导致了 bug。
- 如何用位运算实现"检测无符号加法是否溢出"?
点击查看答案
基础题
(unsigned) -1 = UINT_MAX = 4294967295(32 位)。T2U_4(-8) = -8 + 16 = 8(因为-8的位模式1000无符号解释就是 8)。U2T_4(15) = 15 - 16 = -1(因为 15 的位模式1111补码解释是 -1)。- 有符号数被隐式转换为无符号数。
- 无符号类型。
理解题
- 不是主动的"设计选择",而是 C 标准中"通常算术转换"规则的历史结果:当一个
unsigned int和一个int在一起时,int被转为unsigned int。 - 因为
-x的补码位模式(4 位)等于2^w - x的二进制。例如-1的位模式是1111=15 = 16 - 1 = 2^4 - 1。所以T2U:当 x<0 时,位模式 =2^w + x(x 为负,2^w + x为正)。 strlen(s)返回size_t(无符号)。空字符串strlen(s)=0,0-1在无符号下溢出变成SIZE_MAX,如果将其作为循环计数或缓冲区大小,后果灾难性。- Java 设计者认为无符号数带来的混淆超过其收益,且 Java 的目标是"简单和可预测",避免 C 中的这类陷阱。
- 因为位模式在两种解释下进行二进制加法得到完全相同的结果------有符号和无符号的加法在位级上是同一套操作(二进制加法 + 截断到 w 位)。这是补码设计最巧妙的地方。
思考题
- 好处:大量隐式转换 bug 消失。代价:大量遗留代码需要显式转换,迁移成本极高。Rust 实际上在这方面做了更严格的控制。
- 如果
u > INT_MAX(对于 w 位),则U2T(u) = u - 2^w会得到一个负数。检查方法:if (u > (unsigned) INT_MAX) { /* 会发生符号变化 */ }。 - 返回类型为
int,先判断两个操作数的大小关系避免无符号溢出:当b是正数且大于INT_MAX + a时会溢出;当b是负数且小于INT_MIN + a时也会溢出。
面试/表达题
T2U_w(x) = x + 2^w (x < 0);T2U_w(x) = x (x ≥ 0)。U2T_w(u) = u - 2^w (u > TMax_w);U2T_w(u) = u (u ≤ TMax_w)。位模式保持不变,只是解释函数不同。- 当算术运算的两个操作数类型不同时,C 执行隐式类型提升:如果任一操作数是
unsigned,两者都转换为unsigned;否则转为更宽的类型(如int→long→long long)。 INT_MIN = -2147483648,跟无符号数2147483647U比较时,INT_MIN被转为无符号:(unsigned) (-2147483648) = 2147483648U。2147483648U < 2147483647U= 假 。又是-1 < 0U的翻版。- XDR 安全漏洞(旁注):
ele_cnt * ele_size用size_t(无符号),特定的参数组合会导致乘法溢出,只分配 4096 字节而非 4GB,导致缓冲区溢出。影响了 Sun RPC、Internet Explorer、Kerberos 等大量软件。 - 参考 2.3.1 节:
s = x + y(无符号),当s < x(或s < y)时发生溢出------因为正常加法 s≥x,溢出时 s=x+y-2^w<x(因为 y<2^w,所以 y-2^w<0)。
3.8 知识点 8:整数运算------加法溢出与乘法
3.8.1 ① 什么是它(一句话)
无符号加法 溢出的效果是"模 2^w"(结果 = 真实和 mod 2^w)。补码加法 溢出有两种:正溢出(两个正数相加得到负数)和负溢出(两个负数相加得到正数)。乘法比加法更容易溢出,但编译器会用移位和加减法组合来优化乘常数。
3.8.2 ② 大白话解释
溢出的故事:你有一个 4 位的电子计数器,显示范围 00001111(015)。
- 无符号:9 + 12 = 21,但 21 =
10101(5 位),计数器只能显示 4 位,截掉最高位的 1,显示0101= 5。9 + 12 = 5?这就是"模 16"的效果(21 mod 16 = 5)。 - 补码:9 + 7 = 16,但 16 无法用 4 位补码表示(最大是 7),结果是
1000= -8。9 + 7 = -8 ?这是正溢出。反过来,-8 + (-5) = -13,但最小是 -8,截断后是0011= 3。-8 + (-5) = 3?这是负溢出。
检测溢出的技巧:
- 无符号:
s = x + y,如果s < x就溢出了。 - 补码:两个正数相加得负 → 正溢出;两个负数相加得正 → 负溢出。
| 生活例子 | 对应关系 | 它帮你理解什么 | 类比的边界 |
|---|---|---|---|
| 汽车里程表 | 里程表到 999999 再走 1 公里变成 000000 | 溢出 = 转回起点(模运算) | 里程表是十进制,计算机是二进制 |
| 24 小时钟 | 23 点 + 5 小时 = 4 点(模 24) | 模加法形成阿贝尔群 | 时间溢出没有"符号"概念 |
| 银行账户限额 | 存款 90 亿 + 存款 50 亿 = 140 亿(但 32 位只能存 43 亿) → "余额变成负数" | 大数据运算必须注意类型宽度 | 银行不会截断,会拒绝交易 |
3.8.3 ③ 为什么会有它
- 为什么允许溢出而不是报错:硬件层面上,截断高位比检测溢出再发信号简单得多。大多数 CPU 确实会设置溢出标志位(如 x86 的 OF),但 C 语言不会自动检查。
- 溢出检测的重要性 :XDR 安全漏洞就是因为
ele_cnt * ele_size溢出导致只分配了 4096 字节而非 4GB,攻击者可以越界写入。Ariane 5 火箭爆炸是因为 64 位浮点转 16 位整数溢出。
3.8.4 ④ 关键运算规则
渲染错误: Mermaid 渲染失败: Parse error on line 3: ... B -->|无符号| C[结果 = (a + b) mod 2^w
-----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'
3.8.5 ⑤ 图片说明
建议插入的图片
- 无符号加法的"山坡图":x 轴、y 轴取 0~15,z 轴是 x+y,正常区域和溢出区域用不同颜色
- 补码加法坐标图:4 位下,x+y 在平面上形成的菱形区域,正溢出和负溢出区域高亮
3.8.6 ⑥ 容易混淆的概念
| 对比维度 | 无符号溢出 | 补码正溢出 | 补码负溢出 |
|---|---|---|---|
| 条件 | x + y ≥ 2^w | 正 + 正 = 负 | 负 + 负 = 正 |
| 结果 | 真实和 - 2^w | 真实和 - 2^w | 真实和 + 2^w |
| 检测 | sum < x |
x>0 && y>0 && sum<0 |
x<0 && y<0 && sum>0 |
3.8.7 ⑦ 面试会怎么问
问题 :如何检测两个 int 相加是否溢出?
回答 :分别判断正溢出和负溢出的条件。如果 a > 0 && b > 0 && a + b < 0,则正溢出;如果 a < 0 && b < 0 && a + b >= 0,则负溢出。但注意:有符号溢出在 C 中是未定义行为,所以这种检测本身在标准 C 中不可靠------应该转为无符号数运算后再判断。
3.8.8 ⑧ 一句话总结
!note
溢出不会报错,只会"默默地"截断。无符号溢出是模 2^w 的效果;补码溢出会让符号"翻脸"。编译器利用移位+加减法优化乘除,这些优化在位级上与溢出行为一致。
3.8.9 知识点 8 自测 🧠
3.8.9.1 基础题(5 题)
- 4 位无符号加法:
9 + 12的结果是多少? - 4 位补码加法:
5 + 5的结果是多少? - 4 位补码加法:
-8 + (-5)的结果是多少? - 无符号加法溢出怎么检测?
- 乘以 2^k 等价于什么位运算?
3.8.9.2 理解题(5 题)
- 无符号加法是否满足交换律和结合律?
- 为什么
x - y < 0和x < y在补码下可能不等价? - 为什么编译器把
x * 14优化为(x<<4) - (x<<1)是正确的? - 补码除法
x / 2^k为什么不能直接用x >> k? - 为什么无符号和补码的乘法在位级上是相同的?
3.8.9.3 思考题(3 题)
- 如果 CPU 提供了"溢出标志位",C 语言为什么不直接利用它来抛异常?
- 设计一个安全乘法
safe_multiply(a, b, *overflow)来检测乘法溢出。 - Rust 语言如何处理整数溢出?与 C 的处理方式对比。
3.8.9.4 面试/表达题(5 题)
- 用 C 代码写出无符号加法的溢出检测函数
uadd_ok。 - 用 C 代码写出补码加法的溢出检测函数
tadd_ok。 - 为什么大多数 64 位系统的
int还是 32 位? - 为什么编译器不总是用移位优化乘法?
- 解释"补码的非":为什么
-x = ~x + 1?这对于所有 x 都成立吗?
点击查看答案
基础题
- 21 mod 16 = 5。
- 10 超出了 7,截断后
1010的高位是 1 → -8+2 = -6(或:10-16 = -6)。 - -13 + 16 = 3。
s = x + y;if (s < x) { /* overflow */ }(因为 s = x+y-2^w < x+y-y = x)。- 左移:
x << k。
理解题
- 无符号加法满足交换律和结合律------它形成阿贝尔群(模 2^w 加法)。
- 因为有补码溢出的情况。例如
INT_MIN - 1会溢出为一个正数,导致x - y < 0与x < y结果不一致。编译器在优化时必须谨慎处理这种边界情况。 14 = 16 - 2 = 2^4 - 2^1。x*16 = x<<4,x*2 = x<<1,所以x*14 = (x<<4) - (x<<1)。即使溢出,移位+减法的位级结果与乘法一致------这是由整数算术的代数属性保证的。- 补码除法向零舍入(正数向下、负数向上),而算术右移总是向下舍入。对于负数(如
-7/2 = -3,但-7>>1 = -4),直接移位的方向不对。需加偏置:(x + (1<<k) - 1) >> k。 - 二进制乘法对位模式的操作是相同的------不论是
0110 × 0101还是-2 × 5,位级乘法+截断的结果完全一致。这就是补码设计最优雅的地方。
思考题
- 因为 C 的设计理念是"信任程序员"和高性能------自动检查溢出会降低每次算术运算的速度。其他语言(如 Ada、Rust Debug 模式)会选择不同的权衡。
- 判断:如果
a > 0 && b > 0 && a > INT_MAX / b→ 正溢出;如果a > 0 && b < 0 && b < INT_MIN / a→ 负溢出,等等。转为无符号乘法或使用__builtin_mul_overflow()。 - Rust Debug 模式下整数溢出会 panic(类似断言失败);Release 模式下默认是 wrapping(模 2^w 的截断)。还提供显式方法:
wrapping_add()、checked_add()(返回Option)、saturating_add()(饱和加法)。
面试/表达题
int uadd_ok(unsigned x, unsigned y) { return x + y >= x; }int tadd_ok(int x, int y) { int sum = x + y; int neg_overflow = x < 0 && y < 0 && sum >= 0; int pos_overflow = x > 0 && y > 0 && sum < 0; return !neg_overflow && !pos_overflow; }。但要注意,补码溢出在 C 中是未定义行为,理论上这段代码不可靠。更安全的方法是将操作数转为无符号再判断。- 历史兼容性------几十年的代码假设
int是 32 位。改为 64 位会破坏大量程序。如果要用 64 位整数,用long long或int64_t。 - 当移位和加减法的组合数量超过乘法指令的相对成本时,直接用乘法指令更快。例如
x * 3用(x<<1) + x(两个指令)可能不如一条乘法指令。 ~x + 1 = (2^w - 1 - x) + 1 = 2^w - x,而-x在补码下就是2^w - x(模 2^w 下的逆元)。不成立的例外:INT_MIN(如 32 位下-2147483648),-INT_MIN = INT_MIN(溢出,没有对应的正数)。
3.9 知识点 9:浮点数------IEEE 754 格式
3.9.1 ① 什么是它(一句话)
IEEE 754 是浮点数的工业标准,用 V = (-1)^s × M × 2^E 的形式编码实数。float(32 位)和 double(64 位)分别对应单精度和双精度浮点。
3.9.2 ② 大白话解释
IEEE 754 把一个浮点数分成三个部分:
- 符号位 s(1 位):0→正数,1→负数。
- 阶码 E(8 位 float / 11 位 double):表示 2 的多少次幂。范围大约是 10^(-38) 到 10^(38)。
- 尾数 M(23 位 float / 52 位 double):表示"精度",即数字的有效位数。
三种情况:
- 规格化数(最常见):E = e - Bias,M = 1 + f(隐含一个 1 在前面)。
- 非规格化数(极小的数):E = 1 - Bias,M = f(没有隐含的 1)。用来"逐渐下溢"到 0。
- 特殊值 :指数全 1,尾数全 0 → ±∞;指数全 1,尾数非零 → NaN(如
sqrt(-1)、0/0)。
| 生活例子 | 对应关系 | 它帮你理解什么 | 类比的边界 |
|---|---|---|---|
| 科学记数法 | 3.14×10^8 → (-1)^0 × 3.14 × 10^8。浮点就是二进制的科学记数法 | 规格化数 = "规范化"的科学记数 | 科学记数法以 10 为底,浮点以 2 为底 |
| 游标卡尺 | 尾数位数 → 卡尺精度(能多细);阶码范围 → 卡尺长度(能多长) | 精度和范围是独立的两个维度 | 卡尺是线性的,浮点间距不均匀(接近 0 更密) |
| GPS 坐标 | 经纬度可以表示为"度.分.秒",浮点也一样------精度有限,只能逼近真实位置 | 浮点只是近似,不是精确值 | GPS 定位还有信号误差 |
3.9.3 ③ 为什么会有它
- 如果没有 IEEE 754 标准:1980 年代前,每个厂商有自己的浮点格式------IBM、DEC、Intel 各不相同。程序从一个机器移到另一个机器,浮点结果可能完全不同。IEEE 754 统一了格式,大幅提高可移植性。
- 为什么浮点不能精确表示 0.1 :因为 0.1 = 1/10,而浮点以 2 为底。1/10 在二进制中是无限循环小数(
0.0001100110011...),有限的尾数位数只能截断,产生近似值。就像 1/3 = 0.3333... 在十进制下无法精确表示。
3.9.4 ④ IEEE 754 编码流程
渲染错误: Mermaid 渲染失败: Parse error on line 6: ...值] C --> F按位拼接: s\|exp\|frac D --> F ----------------------^ Expecting 'SQE', 'TAGEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PIPE'
float 格式布局:|s(1)|exp(8)|frac(23)|,Bias=127。
double 格式布局:|s(1)|exp(11)|frac(52)|,Bias=1023。
3.9.5 ⑤ 图片说明
建议插入的图片
- 单精度浮点数和双精度浮点数的位布局对比图(各字段的大小标注)
- 三种类型(规格化、非规格化、特殊值)的分类图
- 8 位迷你浮点格式(4位阶码+3位尾数)的所有非负可表示值分布图:0 附近密集,远离 0 越来越稀疏
3.9.6 ⑥ 容易混淆的概念
| 对比维度 | 整数(补码) | 浮点数(IEEE 754) | 最关键区别 |
|---|---|---|---|
| 表示 | 精确表示有限范围内的整数 | 近似表示实数 | 整数精确,浮点近似 |
| 分布 | 均匀分布(相邻差 1) | 不均匀(接近 0 密度大) | 浮点间距随数值大小变化 |
| 溢出 | 无符号→模 2^w;补码→符号翻转 | 溢出→±∞ | 浮点溢出有专门的"无穷"值 |
| 0 的表示 | 只有一个 0 | 有 +0 和 -0 两个 | 浮点的 ±0 在某些比较中被视为相等 |
3.9.7 ⑦ 面试会怎么问
问题 :为什么 0.1 + 0.2 != 0.3?
回答 :0.1 和 0.2 在 IEEE 754 二进制浮点格式中都无法精确表示------它们都是无限循环的二进制小数,只能取近似值。0.1 ≈ 0.10000000000000000555,0.2 ≈ 0.20000000000000001110,两者的浮点近似值之和略微偏离 0.3 的浮点近似值,所以 0.1 + 0.2 == 0.3 为假。
3.9.8 ⑧ 一句话总结
!note
浮点数 = 科学记数法的二进制版:
(-1)^s × M × 2^E。它范围大但精度有限,靠近 0 更密集,远离 0 越稀疏。0.1 + 0.2 != 0.3不是 bug,而是"有限位数的二进制表示"的必然结果。
3.9.9 知识点 9 自测 🧠
3.9.9.1 基础题(5 题)
- IEEE 754 单精度
float的符号位、阶码位、尾数位各占多少位? - 规格化数的尾数 M 是
1 + f还是f? float的 Bias(偏置量)是多少?- 阶码全 1、尾数非零代表什么值?
- NaN 的全称是什么?
3.9.9.2 理解题(5 题)
- 为什么规格化数尾数要"隐含 1"(即 M = 1 + f)?
- 为什么靠近 0 的浮点数比远离 0 的更密集?
- 非规格化数的两个用途是什么?
- 为什么有 +0 和 -0 两种"零"?它们在什么情况下的行为不同?
- 8 位浮点格式(k=4 阶码位,n=3 尾数位),Bias 是多少?它能表示的最小正规格化数是多少?
3.9.9.3 思考题(3 题)
- 如果让 IEEE 754 的尾数位数翻倍,会影响什么?
- 为什么"爱国者导弹拦截失败"与浮点精度有关?
- 有没有实数在 IEEE 754 浮点中可以被精确表示?
3.9.9.4 面试/表达题(5 题)
- 画图解释单精度浮点数的位布局及各字段含义。
- 将十进制 0.25 转为二进制浮点表示。
- 为什么
float的有效数字大约是 7 位十进制数字? - 解释"规格化数"、"非规格化数"、"∞"、"NaN"四类值的编码规则。
- 浮点数的"逐渐下溢"(gradual underflow)是什么意思?为什么它比直接跳到 0 更好?
点击查看答案
基础题
- s=1, exp=8, frac=23。
- M = 1 + f(隐含开头的 1)。
- 127(= 2^(8-1) - 1 = 2^7 - 1)。
- NaN(Not a Number)。
- Not a Number。
理解题
- "隐含 1"是一个技巧:总是可以调整阶码,使尾数的二进制小数点前恰好是 1.xxx。既然第一位永远是 1,就不需要浪费一位来存它,这就多了一位精度。
- 因为阶码 E 决定了"步长"------规格化数中相邻两个数的间距是
2^E × 2^(-n),E 越小(数越小),间距越小。非规格化数更加密集于 0 附近。 - (1)表示数值 0(+0 和 -0);(2)表示非常接近 0 的数(逐渐下溢)------填补了 0 和最小规格化数之间的缺口。
- IEEE 754 在语义上区分 +0 和 -0:
1/(+0) = +∞,1/(-0) = -∞。但它们相等:+0 == -0为真。在某些极限计算中有意义。 - Bias = 2^(4-1) - 1 = 7。最小正规格化数:E = 1-7 = -6,M=1+0=1,V=2^(-6)×1=1/64≈0.015625。
思考题
- 精度提高(能区分更小的数之间的差异),但存储空间增大,计算效率降低。double 的 52 位尾数已经是大多数科学计算的平衡点。
- 爱国者导弹系统用 24 位寄存器存储 1/10 的近似值,每 0.1 秒加一次。运行 100 小时后,累积误差约 0.34 秒。飞毛腿导弹速度 2000 米/秒,0.34 秒就是 680 米的偏差,导致拦截失败。
- 有的。任何可以写成
x × 2^y(x、y 为整数,x 的二进制位数不超过尾数位数+1)的数都可以精确表示。例如 0.5、0.25、0.125、1.0、2.0、1024 等都可以精确表示。
面试/表达题
- float 的 32 位:最高位 s=符号,接下来 8 位 exp=阶码(含偏置 127),最后 23 位 frac=尾数的小数部分。值 = (-1)^s × M × 2^E,其中 M 和 E 的取值由 exp 是否全 0 或全 1 决定。
- 0.25 = 1/4 = 2^(-2) = 1.0 × 2^(-2)。符号 s=0。E = -2,e = E + Bias = -2 + 127 = 125 = 01111101。M = 1.0,f = 0。位模式:0 01111101 00000000000000000000000。
- 23 位尾数 + 隐含 1 = 24 位二进制有效数字 ≈ 24 × log10(2) ≈ 7.22 位十进制有效数字。所以 float 大约能保证 7 位十进制精度。
- 规格化(exp 不全 0 不全 1):E=e-Bias,M=1+f。非规格化(exp 全 0):E=1-Bias,M=f。∞(exp 全 1,frac 全 0)。NaN(exp 全 1,frac 非零)。
- 如果没有非规格化数,0 和最小规格化数之间会有一个"跳跃"(约 1.2×10^(-38) 的突变)。非规格化数用"逐渐变小"的尾数填补这个区间,让数值分布均匀地接近 0,这在数值计算中(如迭代接近 0 的过程)非常重要。
4 本章完整核心过程 🔄
本章的核心过程是:"一个 C 语言整数的生命历程"------从源代码到内存,再到运算结果。
#mermaid-svg-4jYGm9iS5JSM4JdW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4jYGm9iS5JSM4JdW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4jYGm9iS5JSM4JdW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4jYGm9iS5JSM4JdW .error-icon{fill:#552222;}#mermaid-svg-4jYGm9iS5JSM4JdW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4jYGm9iS5JSM4JdW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4jYGm9iS5JSM4JdW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4jYGm9iS5JSM4JdW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4jYGm9iS5JSM4JdW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4jYGm9iS5JSM4JdW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4jYGm9iS5JSM4JdW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4jYGm9iS5JSM4JdW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4jYGm9iS5JSM4JdW .marker.cross{stroke:#333333;}#mermaid-svg-4jYGm9iS5JSM4JdW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4jYGm9iS5JSM4JdW p{margin:0;}#mermaid-svg-4jYGm9iS5JSM4JdW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4jYGm9iS5JSM4JdW .cluster-label text{fill:#333;}#mermaid-svg-4jYGm9iS5JSM4JdW .cluster-label span{color:#333;}#mermaid-svg-4jYGm9iS5JSM4JdW .cluster-label span p{background-color:transparent;}#mermaid-svg-4jYGm9iS5JSM4JdW .label text,#mermaid-svg-4jYGm9iS5JSM4JdW span{fill:#333;color:#333;}#mermaid-svg-4jYGm9iS5JSM4JdW .node rect,#mermaid-svg-4jYGm9iS5JSM4JdW .node circle,#mermaid-svg-4jYGm9iS5JSM4JdW .node ellipse,#mermaid-svg-4jYGm9iS5JSM4JdW .node polygon,#mermaid-svg-4jYGm9iS5JSM4JdW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4jYGm9iS5JSM4JdW .rough-node .label text,#mermaid-svg-4jYGm9iS5JSM4JdW .node .label text,#mermaid-svg-4jYGm9iS5JSM4JdW .image-shape .label,#mermaid-svg-4jYGm9iS5JSM4JdW .icon-shape .label{text-anchor:middle;}#mermaid-svg-4jYGm9iS5JSM4JdW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4jYGm9iS5JSM4JdW .rough-node .label,#mermaid-svg-4jYGm9iS5JSM4JdW .node .label,#mermaid-svg-4jYGm9iS5JSM4JdW .image-shape .label,#mermaid-svg-4jYGm9iS5JSM4JdW .icon-shape .label{text-align:center;}#mermaid-svg-4jYGm9iS5JSM4JdW .node.clickable{cursor:pointer;}#mermaid-svg-4jYGm9iS5JSM4JdW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4jYGm9iS5JSM4JdW .arrowheadPath{fill:#333333;}#mermaid-svg-4jYGm9iS5JSM4JdW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4jYGm9iS5JSM4JdW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4jYGm9iS5JSM4JdW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4jYGm9iS5JSM4JdW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4jYGm9iS5JSM4JdW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4jYGm9iS5JSM4JdW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4jYGm9iS5JSM4JdW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4jYGm9iS5JSM4JdW .cluster text{fill:#333;}#mermaid-svg-4jYGm9iS5JSM4JdW .cluster span{color:#333;}#mermaid-svg-4jYGm9iS5JSM4JdW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4jYGm9iS5JSM4JdW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4jYGm9iS5JSM4JdW rect.text{fill:none;stroke-width:0;}#mermaid-svg-4jYGm9iS5JSM4JdW .icon-shape,#mermaid-svg-4jYGm9iS5JSM4JdW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4jYGm9iS5JSM4JdW .icon-shape p,#mermaid-svg-4jYGm9iS5JSM4JdW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4jYGm9iS5JSM4JdW .icon-shape .label rect,#mermaid-svg-4jYGm9iS5JSM4JdW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4jYGm9iS5JSM4JdW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4jYGm9iS5JSM4JdW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4jYGm9iS5JSM4JdW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 大端法
小端法
x + 1
x * 2
与 unsigned 混用
C语言源代码中声明 int x = -12345
编译器确定目标字长和字节序
编码为补码位模式
-12345 → 0xCFC7 16位
存储在内存中
高地址: 0xC7, 低地址?: 0xCF
低地址: 0xC7, 高地址?: 0xCF
运行时加载到寄存器
参与运算
0xCFC7 + 0x0001
= 0xCFC8 = -12344
编译器优化为 x << 1
0xCFC7 << 1 = 0x9F8E
隐式转换为 4294952951
此时 -1 < 0U 为假!
逐步说明:
- 声明→编码 :编译器根据目标平台(32 位/64 位)和数据类型的"典型大小"确定用几个字节。
int通常是 4 字节(32 位),编码为补码------最高位是符号位(负权)。 - 编码→存储 :把 4 个字节按目标平台的字节序放进内存。Intel/AMD 用小端法(低字节在低地址),数据从内存读取时必须知道字节序才能正确还原。
- 存储→运算:CPU 从内存加载到寄存器,ALU 执行算术/逻辑运算。补码加法器不需要知道数字是"有符号"还是"无符号"------位级操作完全相同。
- 运算结果→解释:同样的位模式,既可以解释为有符号数(补码),也可以解释为无符号数。当两者混用时,C 语言隐式将两者都转为无符号------这是"潜规则"。
- 溢出:整个过程中最容易误解的环节。CPU 不会报错------它只是丢弃超出的位。程序员必须自己检查是否溢出,或使用安全的编程模式。
5 本章重点概念关系图 🔗
#mermaid-svg-szQz1GW108QTOFyD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-szQz1GW108QTOFyD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-szQz1GW108QTOFyD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-szQz1GW108QTOFyD .error-icon{fill:#552222;}#mermaid-svg-szQz1GW108QTOFyD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-szQz1GW108QTOFyD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-szQz1GW108QTOFyD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-szQz1GW108QTOFyD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-szQz1GW108QTOFyD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-szQz1GW108QTOFyD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-szQz1GW108QTOFyD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-szQz1GW108QTOFyD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-szQz1GW108QTOFyD .marker.cross{stroke:#333333;}#mermaid-svg-szQz1GW108QTOFyD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-szQz1GW108QTOFyD p{margin:0;}#mermaid-svg-szQz1GW108QTOFyD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-szQz1GW108QTOFyD .cluster-label text{fill:#333;}#mermaid-svg-szQz1GW108QTOFyD .cluster-label span{color:#333;}#mermaid-svg-szQz1GW108QTOFyD .cluster-label span p{background-color:transparent;}#mermaid-svg-szQz1GW108QTOFyD .label text,#mermaid-svg-szQz1GW108QTOFyD span{fill:#333;color:#333;}#mermaid-svg-szQz1GW108QTOFyD .node rect,#mermaid-svg-szQz1GW108QTOFyD .node circle,#mermaid-svg-szQz1GW108QTOFyD .node ellipse,#mermaid-svg-szQz1GW108QTOFyD .node polygon,#mermaid-svg-szQz1GW108QTOFyD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-szQz1GW108QTOFyD .rough-node .label text,#mermaid-svg-szQz1GW108QTOFyD .node .label text,#mermaid-svg-szQz1GW108QTOFyD .image-shape .label,#mermaid-svg-szQz1GW108QTOFyD .icon-shape .label{text-anchor:middle;}#mermaid-svg-szQz1GW108QTOFyD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-szQz1GW108QTOFyD .rough-node .label,#mermaid-svg-szQz1GW108QTOFyD .node .label,#mermaid-svg-szQz1GW108QTOFyD .image-shape .label,#mermaid-svg-szQz1GW108QTOFyD .icon-shape .label{text-align:center;}#mermaid-svg-szQz1GW108QTOFyD .node.clickable{cursor:pointer;}#mermaid-svg-szQz1GW108QTOFyD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-szQz1GW108QTOFyD .arrowheadPath{fill:#333333;}#mermaid-svg-szQz1GW108QTOFyD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-szQz1GW108QTOFyD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-szQz1GW108QTOFyD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-szQz1GW108QTOFyD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-szQz1GW108QTOFyD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-szQz1GW108QTOFyD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-szQz1GW108QTOFyD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-szQz1GW108QTOFyD .cluster text{fill:#333;}#mermaid-svg-szQz1GW108QTOFyD .cluster span{color:#333;}#mermaid-svg-szQz1GW108QTOFyD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-szQz1GW108QTOFyD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-szQz1GW108QTOFyD rect.text{fill:none;stroke-width:0;}#mermaid-svg-szQz1GW108QTOFyD .icon-shape,#mermaid-svg-szQz1GW108QTOFyD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-szQz1GW108QTOFyD .icon-shape p,#mermaid-svg-szQz1GW108QTOFyD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-szQz1GW108QTOFyD .icon-shape .label rect,#mermaid-svg-szQz1GW108QTOFyD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-szQz1GW108QTOFyD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-szQz1GW108QTOFyD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-szQz1GW108QTOFyD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 位 bit
字节 byte
十六进制 hex
字节序 大端/小端
数据大小 字长
整数类型
无符号数 unsigned
补码 twos-complement
整数运算
溢出 overflow
移位优化 shift
浮点类型 float/double
IEEE 754
规格化/非规格化/特殊值
舍入/精度损失
T2U/U2T转换
隐式类型转换陷阱
关系解释:
- 位→字节→十六进制:这是信息存储的"语言链"。十六进制是二进制和人类之间的翻译桥梁。
- 字长决定一切:指针大小、地址空间、
long的大小都取决于字长。 - 无符号数和补码"同根同源":同一个位模式,两种解读。它们在加法、乘法等操作上共享同一套硬件。
- T2U/U2T 转换是"陷阱工厂":C 语言不改变位模式,只改变解读规则。这导致
-1 < 0U这种反直觉的结果。 - 浮点数"另起炉灶":与整数完全不同------三段式编码、指数+尾数、有 ±∞ 和 NaN。浮点运算不满足结合律。
6 本章最容易踩坑的地方 ⚠️
!warning 误区 1
很多人误以为 :
sizeof(int)在所有机器上都是 4。实际上 :C 标准只规定
int至少 16 位,实践中 32 位和 64 位平台通常为 32 位(4 字节),但某些嵌入式系统可能是 16 位。用int32_t保证 32 位。为什么容易错:大部分教程和教学环境默认 32 位,新手从未见过例外。
怎么避免 :永远使用
<stdint.h>中的定长类型(int32_t、uint64_t等)来保证精确宽度。
!warning 误区 2很多人误以为 :
&和&&只是写法不同,效果一样。实际上 :
&是按位与(返回多位值),&&是逻辑与(只返回 0 或 1,且有短路求值)。0x10 & 0x01 = 0,0x10 && 0x01 = 1。在条件判断中误用&是常见 bug。为什么容易错:初学 C 语言时,两者的符号外观太像了。
怎么避免 :记住口诀------"双符号逻辑,单符号位级"。
&&/||用于if条件,&/|用于操作二进制位。
!warning 误区 3很多人误以为 :有符号数的右移
>>是全平台统一的。实际上:C 标准不保证有符号数右移是算术右移。实际中几乎所有主流编译器都使用算术右移,但从标准角度看,依赖此行为的代码不可移植。
为什么容易错:在 x86/ARM 平台上都测试通过,就以为"肯定没问题"。
怎么避免 :对有符号数右移后检查行为;或先转为无符号数再移位;或使用
int32_t+ 明确的算术右移实现。
!warning 误区 4很多人误以为 :
-1 < 0U的结果是真。实际上 :结果为假 。因为
-1(有符号)在跟0U(无符号)比较时,被隐式转换为无符号数:(unsigned)(-1) = 0xFFFFFFFF = UINT_MAX = 4294967295,而0U = 0。4294967295 < 0为假。为什么容易错:人脑的算术直觉是"负数 < 正数",但 C 语言的隐式转换规则打破了这种直觉。
怎么避免 :不要混用有符号和无符号数进行比较。用
gcc -Wsign-compare -Wconversion让编译器警告。
!warning 误区 5很多人误以为:整数溢出时程序会崩溃或抛异常。
实际上 :无符号溢出是定义好的行为(模 2^w),有符号溢出在 C 中是未定义行为------编译器可以假设它不会发生,从而生成"意外"的优化结果。
为什么容易错:Python、Java 等高级语言会抛异常或自动升级精度,C 不会。
怎么避免 :检查溢出条件后再运算;使用编译器内置函数如
__builtin_add_overflow();或转为无符号数做运算。
!warning 误区 6很多人误以为 :
0.1 + 0.2 == 0.3应该成立。实际上:在 IEEE 754 二元浮点格式中,0.1 和 0.2 都无法精确表示,它们的近似值之和的近似值与 0.3 的近似值不相等。
为什么容易错:十进制下 0.1+0.2=0.3 天经地义,但浮点用的是二进制。
怎么避免 :永远不要直接用
==比较浮点数。用fabs(a - b) < epsilon做近似比较;或用decimal库做精确十进制小数运算。
!warning 误区 7很多人误以为 :补码中每个数都有对应的负数(
-x)。实际上 :
INT_MIN(32 位下 -2147483648)在补码中没有对应的正数。-INT_MIN = INT_MIN(溢出)。因为正数范围比负数范围少 1。为什么容易错 :取绝对值似乎"不应该"改变符号,但数学上
|-2147483648| = 2147483648超出了 32 位补码的表示范围。怎么避免 :取绝对值前先判断是否为
INT_MIN;或使用更宽的类型(如int64_t)来存绝对值。
!warning 误区 8很多人误以为 :
x << k在任何 k 下都是安全的。实际上 :当
k < 0或k >= w(字长)时,移位是 C 语言的未定义行为 。例如1 << 32在 32 位系统上是未定义的。为什么容易错 :x86 处理器实际会用移位量 mod 字长来执行(所以
1<<32在 x86 上等同于1<<0 = 1),但这只是特定硬件的行为,不是 C 语言的保证。怎么避免 :确保
k在[0, w-1]范围内;或使用显式的if判断。
!warning 误区 9很多人误以为:float 和 double 在内存中用相同的大端/小端规则存储。
实际上:浮点数的字节序不一定与整数相同。某些 ARM 平台整数用小端、浮点用大端("混合端"),尽管现代平台已很少见。
为什么容易错:所有字节序的相关代码通常只测试整数,假设浮点也一样。
怎么避免 :不要假设整数和浮点的字节序一致;用
union或memcpy检查实际存储。
!warning 误区 10很多人误以为:浮点数的加法满足结合律。
实际上 :
(a + b) + c不等于a + (b + c)。例如单精度下(3.14 + 1e20) - 1e20 = 0.0(3.14 被舍入吞没),而3.14 + (1e20 - 1e20) = 3.14。为什么容易错:基本的数学直觉是加法可以随便加括号,但浮点的有限精度打破了这一点。
怎么避免:累加时从小到大加、使用双精度(double)积累、或使用专门的补偿求和算法(Kahan summation)。
7 本章自测(总复习)✅
7.1 基础题(5 题)
- 将
0x2F3C转换为二进制。 - 补码中 8 位模式的
11111111表示什么十进制值? - 在大端法机器上,
0x12345678的第一个字节(最低地址)是什么值? - 无符号数 9 和 12 在 4 位系统上相加的结果是多少?
- IEEE 754 单精度浮点数的 Bias 值是多少?
7.2 理解题(5 题)
- 为什么十六进制在计算机底层编程中如此常用?
- 小端法在 CPU 硬件实现上有什么优势?
- 为什么 C 语言中
sizeof返回的是size_t(无符号类型)而不是int? - 如何解释"同一个位模式可以表示不同的数值"?
- 浮点数为什么不能精确表示 0.1 而整数可以精确表示 0?
7.3 思考题(3 题)
- 如果让你设计一个新编程语言,你会如何处理有符号/无符号混用的问题?
- 在什么场景下选择
float而不是double是合理的? - 补码的"不对称范围"(负数比正数多 1)在哪些算法中会带来麻烦?
7.4 面试/表达题(5 题)
- 解释"补码加法器和无符号加法器是同一个硬件"的原理。
- 给一个新手程序员用大白话讲清楚"为什么
-1 < 0U是假的"。 - IEEE 754 浮点数的"规格化"是什么意思?为什么要有这个设计?
- 写一个 C 函数,判断一个 32 位的补码整数能否被安全地转为
float而不丢失精度。 - 解释"信息存储"的全貌:从"一个位"到"一个浮点数",逐层说明。
点击查看答案
基础题
0x2F3C=0010 1111 0011 1100。11111111补码值 =-128 + 127 = -1。0x12(最高有效字节在最低地址,大端的"大"先来)。(9 + 12) mod 16 = 21 mod 16 = 5。- 127(= 2^7 - 1)。
理解题
- 因为每 4 位二进制精确对应 1 位十六进制(16 = 2^4),转换无歧义且压缩率高。十进制和二进制之间没有这种简单的"分组对应"关系。
- 加法运算从低位到高位逐位进行(进位向高位传播),小端法把低位放在低地址,和加法的计算顺序自然一致。
- 因为
size_t必须能表示任意对象的最大可能大小(包括整个地址空间),它天然需要无符号且宽度等于指针宽度。 - 因为"编码"是解释规则的问题。同一个位模式用
B2U(无符号编码)和B2T(补码编码)得到不同值。位模式是客观事实,解释规则是主观约定。 - 0 =
000...0,这个位模式在所有整数和浮点格式中表示"精确 0"。0.1 在二进制中是0.0001100110011...(无限循环),有限的尾数只能截取近似。
思考题
- Rust 的做法参考:默认不允许隐式有符号↔无符号转换,需要显式的
x as u32。这对于安全性是极大进步,但也增加了书写成本。 - 内存受限(嵌入式、GPU 显存)、对精度要求不高(图形渲染的颜色值)、需要大量存储的数组(机器学习中的梯度累加有时用 float16/float)。
- 取绝对值、求相反数、二分查找的边界值计算(
mid = (low + high) / 2在极端情况溢出)、安全除法中除数为INT_MIN且被除数为-1时的处理。
面试/表达题
- 补码加法在位级上就是普通的二进制加法(从低位到高位,逐位相加,进位传播,最高位同样处理),最后截断到 w 位。无符号加法也是同样的操作。区别在于"你如何看待结果的位模式"------两个解读函数
B2U和B2T给出不同数值,但位模式是一样的。 - C 语言里
-1是int(有符号),0U是unsigned。当它们放到一起比较时,C 把-1自动当成"无符号数"来看-------1的二进制全是 1(111...111),当成无符号数就是最大的整数(约 43 亿)。43 亿 < 0?当然不是!所以结果是"假"。 - "规格化"就是统一把尾数调整为
1.xxx的形式(通过调整阶码),让每个数有唯一的规范化表示。好处:(1)消除重复表示;(2)隐含的 1 省出一位精度;(3)简化硬件比较和运算。 - float 有 24 位有效二进制位(23 位尾数 + 隐含 1),int 有 32 位。如果
abs(x)的二进制中"最高非零位到最低位"的跨度超过 24 位,转 float 就会丢失精度。例如x > 2^24或x < -2^24的 int 转为 float 必然丢失低位。 - 最底层是位 (0/1)→ 8 位组成一个字节 (byte)→ 多个字节组成字 (word),用十六进制 速记 → 字节的排列顺序由字节序 决定 → 同样的字节用不同的编码规则 解读:无符号(纯二进制)、补码(最高位=负权)、IEEE 754 浮点(三段式 V=s×M×2^E)→ 编码后的值参与算术运算,但有限位数会导致溢出 或精度损失。
8 本章速查表 🧾
| 概念 | 一句话解释 | 为什么重要 | 容易混淆什么 |
|---|---|---|---|
| 位(bit) | 最小存储单位,只有 0/1 两种状态 | 一切信息的最终形式 | 字节(byte=8 位) |
| 十六进制 | 每 4 位二进制对应 1 位 hex 数字 | 人和位模式之间的翻译桥梁 | 十进制 |
| 大端法 | 高位字节存在低地址 | 网络字节序,跨平台数据交换 | 小端法 |
| 小端法 | 低位字节存在低地址 | Intel/AMD 默认,PC 的主流 | 大端法 |
| 补码 | 最高位是负权的编码方式 | C 语言 int 的实际存储方式 | 无符号数、反码 |
| 无符号数 | 每位都是正权,只能表示非负数 | 地址、size_t、位掩码等 | 补码(同一个位模式不同解读) |
| T2U 转换 | 补码→无符号,位模式不变,负数变极大的正数 | 隐式转换的根源 | 数值转换(真正改变位模式) |
| 溢出 | 运算结果超出表示范围 | 安全漏洞(XDR)、火箭爆炸(Ariane 5) | 正常截断 |
| IEEE 754 | 浮点数工业标准,V=s×M×2^E | 所有现代 CPU 的 float/double | 整数编码 |
| NaN | 不是一个数,如 sqrt(-1) 的结果 | 表示未定义的计算结果 | ±∞(无穷大) |
| 规格化数 | exp 不全 0 不全 1,M=1+f | 浮点数的主体,覆盖绝大多数正常值 | 非规格化数 |
| 算术右移 | 右移时左边补符号位 | 保持负数符号,除法优化 | 逻辑右移(左边补 0) |
| 掩码 | 用 1 标记"想要"的位 | 位运算的核心应用,权限、标志位等 | 普通整数 |
9 本章总结与下一步 🚀
-
今天真正学会了什么?
- 计算机里的一切------数字、字符、代码------都是位模式 + 编码规则的组合。
- 补码是整数的最优编码:硬件加法器可同时处理有符号和无符号运算,不产生"两个零"。
- C 语言中有符号和无符号混用是隐式的陷阱:位模式不变、解释规则改变,导致反直觉的运算结果。
- 溢出是有限位数的必然结果------CPU 不报错,必须程序员自己检查。
- IEEE 754 浮点数 用
V=s×M×2^E近似实数,精度有限、不可结合,0.1+0.2!=0.3是设计使然。
-
下一章会学什么? 第 3 章"程序的机器级表示"------你将看到 C 代码被编译成汇编指令,理解寄存器、指令格式、地址模式,以及补码和位运算在机器代码中的实际使用方式。
-
建议提前了解什么? 复习十六进制转换(将频繁用到)、熟悉本笔记中的位模式和补码运算、尝试在调试器中查看简单的 C 变量的内存布局(
gdb的x命令)。
!tip 30 秒复述挑战
计算机用 0 和 1 存一切。无符号数 把位当纯数字读,补码 让最高位当"负的"来用所以能表示负数,浮点像二进制科学记数法------分符号、指数、有效数字三部分。C 语言里把有符号数和无符号数放一起比较会"自动改为无符号",导致负数变成巨大正数。运算超过位数会溢出而不报错------这是最隐蔽的 bug 来源。