binary - data - cpu - memory
01. 二进制、数据表示与存储单位
0. 本章先解决什么问题
计算机里只有位模式,为什么能有整数、字符、地址、图片、指令、网络包?
答案是:
同一串 bit,在不同解释规则下,可以代表不同东西。
本章要解决这些基础问题:
- bit、byte、KB、MB、GB 到底是什么?
- 为什么固定位宽决定了整数范围?
- 为什么“字符数”和“字节数”不是一回事?
- 为什么跨机器传二进制数据要关心大端小端?
- 地址是什么,为什么数组能随机访问?
- 为什么很多 bug 本质是“表示规则不一致”?
你以后遇到乱码、溢出、协议解析错误、文件格式错误、数组地址计算、二进制数据跨平台问题,都会回到这一章。
先看同一串字节如何被不同规则解释。后面所有溢出、乱码、字节序和地址问题都从这里展开。
这张图怎么读
读这张图时,不要先问“这个值是多少”,先问三件事:
这段 bit 有多长?
它按什么规则解释?
它位于哪个地址或字段边界?
同一串 bit 可以在不同层变成不同对象:
| 解释层 | 可能含义 |
|---|---|
| 数值 | unsigned、signed、补码、浮点 |
| 文本 | 字符编码里的码点 |
| 地址 | 内存位置或偏移 |
| 指令 | opcode、寄存器编号、立即数 |
| 协议/文件 | 某个字段、长度、标志位 |
所以二进制排障要先拿原始 byte,再确认位宽、符号、字节序、编码和字段边界。
1. bit 和 byte
bit 是最小的信息单位,只能是 0 或 1。
byte 通常是 8 个 bit:
1 byte = 8 bits
8 bit 一共有:
2^8 = 256
种不同状态。
常见单位:
| 单位 | 常见含义 | 说明 |
|---|---|---|
| bit | 1 个二进制位 | 0 或 1 |
| byte | 8 bit | 常见最小寻址单位 |
| KB | 约 1024 bytes | 有些场景也会用 1000 bytes |
| MB | 1024 KB | 文件、内存常见单位 |
| GB | 1024 MB | 内存、磁盘常见单位 |
| TB | 1024 GB | 大容量存储 |
注意:存储厂商、网络带宽、操作系统显示可能使用不同换算口径。网络里常见 Mbps 是 bit per second,不是 byte per second。
100 Mbps ≈ 12.5 MB/s
因为:
100 megabits / 8 = 12.5 megabytes
2. 位模式和解释规则
同一串 bit:
01000001
可以解释成:
| 解释方式 | 意义 |
|---|---|
| 无符号整数 | 65 |
| ASCII 字符 | A |
| 二进制协议字段 | 某个编号或标志 |
| 指令编码的一部分 | 操作码或操作数 |
这说明:
bit 本身没有类型。
类型是解释规则。
程序里的类型、协议字段、文件格式、字符编码,本质都在规定:
这些 bit 应该按什么规则解释?
如果解释规则错了,数据就会“看起来坏掉”。
例子:
- UTF-8 文件按错误编码打开会乱码。
- 网络包字段按错误字节序读取会变成奇怪数字。
- 有符号整数按无符号解释会得到完全不同的值。
- 压缩文件按普通文本打开会出现不可读字符。
机制深挖:类型不是 bit 的属性,而是读写双方的契约
内存里不会存“这是整数”“这是字符”“这是指针”这样的标签。内存保存的是 byte。类型存在于读写规则里:
写入方按某种规则编码
读取方必须按同一种规则解码
只要两边规则不同,同一段 byte 就会变成另一个意义。
| 写入方以为 | 读取方以为 | 结果 |
|---|---|---|
| 4 byte 小端整数 | 4 byte 大端整数 | 数值巨大变化 |
| UTF-8 文本 | 单字节本地编码 | 乱码 |
| 无符号长度 | 有符号长度 | 大长度可能变负 |
| 紧凑二进制结构 | 带对齐填充结构 | 字段整体错位 |
| 字节长度 | 字符数量 | 截断或越界 |
这就是为什么底层协议和文件格式必须写清:
字段宽度
符号规则
字节序
编码
对齐/填充
长度单位
非法值处理
所谓“数据坏了”,很多时候不是 byte 被破坏,而是解释契约不一致。
3. 固定位宽:状态数量决定范围
如果用 n 个 bit 表示一个无符号整数,一共有:
种状态。
无符号整数范围:
0 到 2^n - 1
例子:
| 位数 | 状态数 | 无符号范围 |
|---|---|---|
| 8 bit | 256 | 0 到 255 |
| 16 bit | 65536 | 0 到 65535 |
| 32 bit | 4294967296 | 0 到 4294967295 |
如果要表示有符号整数,就必须拿一部分状态表示负数。常见补码范围是:
-2^(n-1) 到 2^(n-1)-1
例如 8 bit 补码:
-128 到 127
这就是固定位宽的核心:
位数越多,可表示状态越多。
位数固定,范围就固定。
4. 溢出的第一性解释
溢出不是神秘错误,而是状态数量不够。
8 bit 无符号整数最大是:
11111111 = 255
如果再加 1:
11111111
- 00000001
1 00000000
如果只保留低 8 bit,就得到:
00000000
结果从 255 回到 0。
所以溢出的底层机制是:
计算结果需要更多 bit 表示
但机器只保留固定位数
高位被截断
这会影响:
- 计数器。
- 时间戳。
- 文件大小。
- 数组下标。
- 哈希计算。
- 金额累计。
- 二分查找中点计算。
5. 字符编码:字符和数字之间的协议
计算机不认识“字”,只认识数字。字符编码就是把字符映射到数字的规则。
常见概念:
| 概念 | 含义 |
|---|---|
| 字符 | 用户认为的文字单位 |
| 码点 | Unicode 给字符分配的编号 |
| 编码 | 把码点变成字节序列的规则 |
| 字节序列 | 文件、网络里真实保存和传输的内容 |
常见编码:
| 编码 | 特点 |
|---|---|
| ASCII | 早期英文字符,7 bit 即可表示基本字符 |
| Unicode | 给全球字符分配统一码点 |
| UTF-8 | 变长编码,兼容 ASCII,互联网常用 |
| UTF-16 | 变长编码,某些系统和运行时内部常见 |
UTF-8 是变长编码:
英文字符通常 1 byte
很多中文字符通常 3 bytes
部分符号可能更多 bytes
所以:
字符数 != 字节数
这会影响:
- 文件大小。
- 网络传输长度。
- 数据库字段长度。
- 字符串截断。
- 光标位置。
- 协议里的 length 字段。
6. 用户感知字符也不总等于一个码点
更细一点,用户看到的“一个字符”有时可能由多个码点组成。
例如:
- 一个基础字符加一个组合音标。
- 某些表情符号由多个码点组合。
- 地区旗帜可能由两个区域符号组合。
所以有三种不同长度:
| 长度 | 说明 |
|---|---|
| 字节长度 | 存储和传输占多少 byte |
| 码点数量 | Unicode 编号数量 |
| 用户感知字符数量 | 人眼看到的字符簇数量 |
不要看到“字符串长度”就默认它代表用户看到的字符数。要看具体系统定义。
7. 大端和小端:多字节数字的存放顺序
一个 byte 只有 8 bit。更大的整数需要多个 byte。
例如:
0x12345678
需要 4 个 byte:
12 34 56 78
问题是:低地址先放哪一部分?
| 字节序 | 低地址放什么 | 示例 |
|---|---|---|
| 大端 big-endian | 高位字节 | 12 34 56 78 |
| 小端 little-endian | 低位字节 | 78 56 34 12 |
字节序影响:
- 二进制文件解析。
- 网络协议解析。
- 跨平台数据交换。
- 内存 dump 阅读。
- 手写序列化/反序列化。
网络字节序通常是大端。解析协议时不能靠“本机刚好这么存”。
正确思路:
外部格式明确字节序
程序按格式解析
不要隐式依赖本机内存布局
手推:01 00 00 00 到底是多少
给定 4 个 byte:
01 00 00 00
按大端解释:
0x01000000 = 16777216
按小端解释:
0x00000001 = 1
byte 没变,值差了 1600 多万倍。再进一步,如果这个字段代表 payload 长度:
| 解释 | 后果 |
|---|---|
| 小端长度 1 | 读取 1 byte body |
| 大端长度 16777216 | 试图读取 16 MB body,可能阻塞、超限或 OOM |
所以二进制协议解析里,字节序不是边角知识,而是安全边界。只要 length、offset、count 这类字段解释错,后续所有字段都会跟着错位。
8. 地址是什么
地址可以理解为内存中某个 byte 的编号。
如果内存像一排格子:
代码块收起展开
地址: 1000 1001 1002 1003 1004
内容: ?? ?? ?? ?? ??地址就是格子的编号。
数组随机访问快,是因为元素地址可以计算:
元素地址 = 起始地址 + 下标 * 元素大小
例如一个元素占 4 byte:
a[0] 地址 = base + 0 * 4
a[1] 地址 = base + 1 * 4
a[2] 地址 = base + 2 * 4
这解释了两个点:
- 数组要求元素连续存放。
- 随机访问只需要一次地址计算。
链式结构不一样:
当前节点
-> 读取 next 地址
-> 跳到下一个节点
下一个元素在哪里,不能直接用下标算出来。
9. 对齐:为什么数据不一定紧密排列
硬件访问某些数据时,希望地址满足对齐要求。
例如 4 byte 整数最好放在 4 的倍数地址上:
- 地址 1000:OK
- 地址 1001:可能不理想或需要额外处理
原因是硬件按块读取数据。未对齐访问可能需要多次读取再拼接。
结构化数据里常见填充:
字段 A: 1 byte
填充: 3 bytes
字段 B: 4 bytes
这会导致:
字段大小之和 != 结构实际占用大小
对齐影响:
- 内存占用。
- 二进制协议布局。
- 文件格式。
- 跨语言结构传递。
- CPU 访问效率。
10. 联系实际:遇到二进制数据问题怎么排查
如果一个二进制文件、网络包、内存数据解析出来不对,按这个顺序问:
- 这段数据的单位是 bit、byte,还是字符?
- 字段长度是多少?是固定长度还是变长?
- 整数是有符号还是无符号?
- 使用大端还是小端?
- 字符串使用什么编码?
- length 字段表示字节数、字符数,还是元素个数?
- 结构中有没有对齐和填充?
- 读取位置是否偏移了一两个 byte?
- 数据是否被截断或多读?
很多“数据怪问题”不是计算错,而是解释规则错。
边界条件:length 是字节数、字符数,还是元素个数?
二进制和内存问题里,length 这个词非常危险,因为它可能指不同单位。
同一段内容:
数据 = 4 个整数
每个整数 = 4 byte
那么:
元素个数 = 4
字节数 = 16
如果写入方说 length = 4 表示元素个数,读取方却按字节数理解,就只会读取 4 byte,也就是只读到第一个整数。反过来,如果写入方的 length = 16 表示字节数,读取方按元素个数理解,就会尝试读取 16 个整数,也就是 64 byte。
字符串更容易出错:
“A” 可能是 1 byte / 1 个字符
“你” UTF-8 下通常是 3 byte / 1 个字符
“é” 视觉上 1 个字符,底层可能是多个码点组合
所以跨边界格式必须把单位写死:
| 字段 | 必须说明 |
|---|---|
length | 字节数、元素数、码点数,还是用户感知字符数 |
offset | 从文件开头、当前段开头,还是某个结构体开头算 |
count | 记录数、数组元素数,还是剩余可读数量 |
size | 单个元素大小,还是整段 payload 大小 |
这类 bug 的典型指纹是:
ASCII 输入正常;
一出现多字节字符、结构数组、跨平台文件,就开始错位或截断。
排查时不要只问“长度是多少”,要问“以什么单位计数”。
手推:地址计算错一个元素大小,会怎样一路越界
数组随机访问依赖公式:
元素地址 = base + index * element_size
假设:
base = 1000
element_size = 4 byte
index = 3
正确地址是:
1000 + 3 * 4 = 1012
如果错误地把元素大小当成 1 byte:
1000 + 3 * 1 = 1003
你读到的就不是第 3 个元素,而是落在第 0 个元素内部的某个 byte 附近。后果包括:
| 错误 | 可能表现 |
|---|---|
| 元素大小算错 | 读到字段中间,值完全异常 |
| 对齐被破坏 | 某些硬件上变慢或异常 |
| 结构 padding 被忽略 | 后续字段全部错位 |
| index 边界没检查 | 访问数组外的内存 |
这说明“数组 O(1)”不是魔法。它成立的前提是:
代码块收起展开
元素大小固定;
起始地址正确;
下标在范围内;布局规则一致。
任何一个前提坏掉,O(1) 地址计算仍然会很快,但会很快地读错地方。
小实验:同一串字节的多重解释
给定 4 个字节:FF FF FF 7F。尝试分别回答:
| 解释方式 | 你要问的问题 |
|---|---|
| 无符号整数 | 这是从低位到高位存,还是从高位到低位存? |
| 有符号整数 | 最高位是否作为符号位?补码规则是什么? |
| 字符数据 | 这些字节属于哪种编码?是否能组成合法字符? |
| 内存地址片段 | 地址宽度是多少?对齐要求是什么? |
这个练习的目的不是记住某个固定答案,而是形成一个习惯:二进制本身只保存位模式,意义来自解释规则。排查乱码、溢出、协议字段、文件格式、跨平台数据时,第一步永远是确认“这些 bit 正在按什么规则被读”。
11. 学完本章你能解决什么问题
学完这一章,你应该能解决或开始分析这些问题:
- bit、byte 和 KB/MB/GB 的区别是什么?
- 为什么同一串 bit 可以代表整数、字符或指令?
- 为什么 n bit 只能表示有限范围?
- 溢出为什么会发生?
- 为什么字符数、码点数、字节数可能不同?
- 为什么二进制协议要明确大端小端?
- 地址是什么,数组为什么能 O(1) 随机访问?
- 对齐和填充为什么会让结构占用变大?
- 如何排查乱码、协议解析错误、二进制字段错位这类问题?
记住这条主线:
位模式没有天然意义。
意义来自约定。
错误往往来自约定不一致。