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

种不同状态。

常见单位:

单位常见含义说明
bit1 个二进制位0 或 1
byte8 bit常见最小寻址单位
KB约 1024 bytes有些场景也会用 1000 bytes
MB1024 KB文件、内存常见单位
GB1024 MB内存、磁盘常见单位
TB1024 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 表示一个无符号整数,一共有:


2

种状态。

无符号整数范围:

0 到 2^n - 1

例子:

位数状态数无符号范围
8 bit2560 到 255
16 bit655360 到 65535
32 bit42949672960 到 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 的编号。

如果内存像一排格子:

代码块PLAINTEXT · 2 行收起展开
地址:  1000 1001 1002 1003 1004
内容:   ??   ??   ??   ??   ??

地址就是格子的编号。

数组随机访问快,是因为元素地址可以计算:

元素地址 = 起始地址 + 下标 * 元素大小

例如一个元素占 4 byte:

a[0] 地址 = base + 0 * 4
a[1] 地址 = base + 1 * 4
a[2] 地址 = base + 2 * 4

这解释了两个点:

  1. 数组要求元素连续存放。
  2. 随机访问只需要一次地址计算。

链式结构不一样:

当前节点
-> 读取 next 地址
-> 跳到下一个节点

下一个元素在哪里,不能直接用下标算出来。

9. 对齐:为什么数据不一定紧密排列

硬件访问某些数据时,希望地址满足对齐要求。

例如 4 byte 整数最好放在 4 的倍数地址上:

  • 地址 1000:OK
  • 地址 1001:可能不理想或需要额外处理

原因是硬件按块读取数据。未对齐访问可能需要多次读取再拼接。

结构化数据里常见填充:

字段 A: 1 byte
填充: 3 bytes
字段 B: 4 bytes

这会导致:

字段大小之和 != 结构实际占用大小

对齐影响:

  • 内存占用。
  • 二进制协议布局。
  • 文件格式。
  • 跨语言结构传递。
  • CPU 访问效率。

10. 联系实际:遇到二进制数据问题怎么排查

如果一个二进制文件、网络包、内存数据解析出来不对,按这个顺序问:

  1. 这段数据的单位是 bit、byte,还是字符?
  2. 字段长度是多少?是固定长度还是变长?
  3. 整数是有符号还是无符号?
  4. 使用大端还是小端?
  5. 字符串使用什么编码?
  6. length 字段表示字节数、字符数,还是元素个数?
  7. 结构中有没有对齐和填充?
  8. 读取位置是否偏移了一两个 byte?
  9. 数据是否被截断或多读?

很多“数据怪问题”不是计算错,而是解释规则错。

边界条件: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)”不是魔法。它成立的前提是:

代码块JAVA · 3 行收起展开
元素大小固定;
起始地址正确;
下标在范围内;

布局规则一致。

任何一个前提坏掉,O(1) 地址计算仍然会很快,但会很快地读错地方。

小实验:同一串字节的多重解释

给定 4 个字节:FF FF FF 7F。尝试分别回答:

解释方式你要问的问题
无符号整数这是从低位到高位存,还是从高位到低位存?
有符号整数最高位是否作为符号位?补码规则是什么?
字符数据这些字节属于哪种编码?是否能组成合法字符?
内存地址片段地址宽度是多少?对齐要求是什么?

这个练习的目的不是记住某个固定答案,而是形成一个习惯:二进制本身只保存位模式,意义来自解释规则。排查乱码、溢出、协议字段、文件格式、跨平台数据时,第一步永远是确认“这些 bit 正在按什么规则被读”。

11. 学完本章你能解决什么问题

学完这一章,你应该能解决或开始分析这些问题:

  1. bit、byte 和 KB/MB/GB 的区别是什么?
  2. 为什么同一串 bit 可以代表整数、字符或指令?
  3. 为什么 n bit 只能表示有限范围?
  4. 溢出为什么会发生?
  5. 为什么字符数、码点数、字节数可能不同?
  6. 为什么二进制协议要明确大端小端?
  7. 地址是什么,数组为什么能 O(1) 随机访问?
  8. 对齐和填充为什么会让结构占用变大?
  9. 如何排查乱码、协议解析错误、二进制字段错位这类问题?

记住这条主线:

位模式没有天然意义。
意义来自约定。
错误往往来自约定不一致。

延伸阅读