字节序不影响写入,但会影响内存中的读取

endianness doesn't affect writing but reading in memory

我得出的结论是,无论是小端还是大端。

我们从左到右写入内存,这意味着数字 0x00FF 将在两个系统中写入如下:

1000:00

1001:FF

但是字节顺序的读取不同。

在小端我们将读取这两个字节

1000:00

1001:FF

作为 0xFF00,在大端我们将把它读作 0x00FF

现在你可能会说为什么我会这样做:

mov word [esp],0x00FF

在小端处理器中,结果将是 0x00FF,但我说过在小端处理器中,结果将是 0xFF00,所以它完全揭穿了我所说的。

好吧,看来汇编程序只是将数字反转为 0xFF00 看看:

如果汇编器不反转数字,我们会将其读作 0xFF00。

所以基本上是因为汇编程序将其反转,我们将数字写为

1000:FF

1001:00

在内存中,我们将从最低有效字节开始读取它,因此我们将得到 0x00FF

我说得对吗,还是它的工作方式不同?

字节顺序是跨越多个存储单元(通常是字节)的数值之间的关系,可以表示为一对用于分解和重组的公式——用于将单个值(需要多个字节)转换为字节序列,然后从字节序列返回到单个值。

(字节顺序并没有告诉我们处理器如何执行这些操作,只是告诉我们它们根据下面的公式工作。所以,具体来说,我们不知道用于满足公式的时间顺序是什么;公式与时间无关,但只对序列中的字节顺序敏感。)

比如在16位中我们有一个值0x1234,准备把它作为一个字节序列存入内存,即低字节存放在低地址,高字节存放在高地址, 其中高位地址 = 低位地址 + 1.


以下公式使用little endian分解值:

lower byte  = 0x1234 & 0x00FF            = 0x34
higher byte = 0x1234 / 256 = 0x1234 >> 8 = 0x12

小端重组公式为

value = lower byte + higher byte * 256 = 0x34 + 0x12 * 256 = 0x1234

对于big endian,公式(与little endian相比)交换哪个字节是multiplied/divided:

lower byte  = 0x1234 / 256    = 0x1234 >> 8 = 0x12
higher byte = 0x1234 & 0x00FF =               0x34

并重组:

value = lower byte * 256 + higher byte = 0x12 * 256 + 0x34 = 0x1234

这些公式内置于处理器中并且事先众所周知,因此,当汇编程序在汇编数据时,如:

.data
dw 0x1234

它知道 (1) 这是 16 位数据和 (2) 目标硬件是小端。因此,它将按照小端分解的公式将 0x34、0x12 作为字节放入内存中。 (同样,这不是时间顺序而是相对顺序。)

对于指令,我们可以说汇编程序根据机器码指令集架构对指令和任何需要的立即数进行编码。当立即数被具体化时,它会作为指令解码的一部分返回。由于intel处理器的工作方式,机器代码指令中的编码也会出现小端,但是,编码可能比用汇编语言编写的立即数的全长短。不管怎样,处理器都会在内部重新构造适当的常量,然后使用它。如果将立即数存储到内存中,它将使用小端分解公式来创建要存储的两个字节的序列,就像将寄存器的值存储到内存中一样。


因为公式对(decomp/recomp),读取最后写入的16位位置为16位原始值,返回原始值。只有当我们将该位置视为单个字节时,我们才需要关注字节顺序。

不幸的是,调试器将内存转储为单独的字节,当数据是多字节数据时,使我们暴露于字节顺序。无法仅从内存转储中判断那里存储了什么样的值,是 16 位值还是 8 位值。然而,该信息在程序及其机器代码指令中,以它们处理这些位置的方式(关于它是使用 16 位内存访问还是 8 位内存访问)。


当程序始终以相同的方式使用相同的内存时,它会得到期望值。但是汇编程序中有很多机会出现逻辑错误。此类错误包括使用错误的大小、使用错误的符号、初始化失败。高级语言具有阻止前两种类型的类型,好的语言也具有检测未初始化变量的功能。但是在机器代码中,每条指令都会重复物理存储的相关处理以实现一致性。

(要清楚,将 16 位值视为字节序列并不总是错误,有时这是必要的,即在文件中或 [=58= 中存储数字时]).