在 x86 平台上,指令编码(opcode + ModR/M + …)与 多字节数据的内存存放方式(大小端问题) 是两个不同的概念!
为何会有“8B 00”而不是“00 8B”?
以 mov ax, [bx] 类似的指令为例,它会被汇编器和链接器组合成对应的机器码字节流:
- 第一个字节(较低地址)是 opcode,例如 8B。
- 第二个字节(紧随其后地址)是 ModR/M、SIB 或者紧跟的立即数等字段,这里是 00。
CPU 取指时,会先从内存的最低地址抓取第一个字节 8B,再从紧邻的下一地址抓取第二个字节 00,拼成完整的机器指令“8B 00”并执行。
大小端与“指令读取”无必然联系
x86 上的大小端(Little-Endian)主要体现在“多字节数据(比如 16 位、32 位、64 位整数)的存储顺序”上:即“低位字节”放在内存的低地址,“高位字节”放在内存的高地址。
指令则是由一个个 1 字节 为最小单位的编码序列(opcode + 若干附加字节)组成;CPU 取指时,就按“内存地址递增”的顺序一次取 1 个字节来解码。
也就是说,指令并不是一个需要做‘大小端翻转’的多字节整数,它更像是一串顺次排列的字节序列,CPU 会按顺序抓取和解释每个字节,从而确定完整的指令含义。
换言之,8B 00 并不是说“8B 是高位字节,00 是低位字节”,而是说:
- 第一个字节(内存低地址)= 0x8B (opcode)
- 第二个字节(紧邻地址) = 0x00 (ModR/M 或其他)
如果写成 00 8B,CPU 取到的第一个字节就会变成 0x00,第二个字节才是 0x8B,那么这在指令解码上就完全变了味,要么不合法,要么含义不是你想要的 mov ax, [bx]。
指令编码与大小端的区别
大小端 (Endianness)
对于例如 0x1234(16 位)的数据,在 x86(小端)里会被拆成两个字节存储:
- 地址 + 0: 0x34 (低位字节)
- 地址 + 1: 0x12 (高位字节)
复制代码
这是存放多字节“数据”时的约定。
若读取这两个字节把它当作一个 16 位数,小端模式下 CPU 最终解释为 0x1234。
指令编码 (Instruction Encoding)
对于一条指令来说,每一字节都有特定含义:
- 第一个字节常常是 opcode,
- 第二个字节可能是 ModR/M、或者紧跟的立即数、或者段寄存器选择等字段,
- 第三或第四字节可能是偏移量、立即数的剩余部分……
CPU 取指的过程是:地址从低到高依次读入这几字节,拼凑出“opcode + 附加字段”,从而解析出指令类型、操作数、地址模式等信息。
它跟大小端仅在“指令里如果有多字节立即数字段”时才会有交互关系(比如指令中的立即数 0x1234 也会被拆成 34 12 存在连续地址里),但并不影响 opcode 这一层的排布顺序。
为何有时看似“高位在前”又有时“低位在前”?
当你把内存里的那几个字节当作“机器指令”去看时,往往是从内存低地址到高地址按 人类阅读习惯 直接列出来,比如说:
于是我们说指令是 “8B 00”。
当你把内存里的那几个字节当作“一个多字节数”去看时,x86 小端就会是 “低字节在先,高字节在后”。
所以如果一个 16 位数是 0x1234,放在内存就是 [0x34, 0x12];读到寄存器里就恢复成 0x1234。
这两者本来就服从不同的解释规则,也就不会彼此矛盾。
指令流是一串字节,一个字节接一个字节地存在内存,从 低地址 到 高地址 依次排列。
CPU 取指时也是这样顺序读取,把第一个字节识别成 opcode、第二个字节识别成 ModR/M 或立即数等。
大小端处理的是“把一个多字节数据(如 16 位、32 位、64 位整数)如何分拆到若干字节,并存放到连续的内存地址中”的问题。
因此,“8B 00” 并不违背“小端存储高位在高地址、低位在低地址”的原则;
它只是说明指令的第一个字节(opcode)正好是 0x8B,紧跟的第二个字节是 0x00,两者在汇编→机器码的阶段就被这么顺序写入了内存。
若反过来写成 “00 8B”,它早已不是你想要的指令了。