什么是大小端

Posted 王振龙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是大小端相关的知识,希望对你有一定的参考价值。

计算机以二进制形式将数据存储在内存中。经常被忽视的一件事是此数据的字节级别的格式。这称为字节序,它指的是字节的顺序。

具体来说,little-endian 是将最低有效字节存储在更有效字节之前,而 big-endian 是将最高有效字节存储在较低有效字节之前。

当我们写一个数字(十六进制)时,即0x12345678,我们首先用最高有效字节(12部分)写它。从某种意义上说,大端是写东西的“正常”方式。

这篇文章将只讨论整数的字节序,而不是浮点数,因为它变得更加复杂,定义也更少。

为什么这很重要?

关于字节序的一个重要区别是,它只涉及值如何存储在内存中,而不是我们如何处理值;例如,0x12345678仍然是0x12345678。这里没有字节序的概念。但是,如果我们谈论将这个 4 字节值存储到内存中,那么并且只有这样我们才必须指定字节序。

如果我们使用 little-endian 将前面提到的值存储到内存中,我们将得到以下结果。请注意,每个 2 个十六进制字母代表 1 个字节。

78 56 34 12

如果我们以大端存储它,我们会得到:

12 34 56 78

最后,这就是字节序很重要的原因。因为不知道数据是如何存储的会导致交流不同的值。

例如,所有x86_64处理器(Intel/AMD)都使用小端,而IP/TCP使用大端。这意味着为了让您使用 Internet,您的计算机必须考虑字节顺序的差异。

到目前为止看起来很简单,对吧?

大多数混淆在于小端,所以我们将从那里开始。

提醒一下,little-endianness 是指首先存储最低有效字节的字节顺序。因此,例如,如果我们有 8 字节的值,0x123456789abcdef0我们将按以下方式将其存储在内存中。(注意:我在值旁边放了一个伪内存地址,这样我们就可以说这个值在内存地址处0x00。)

0x00: f0 de bc 9a 78 56 34 12

这里要理解的最重要的事情是我们正在存储一个 8 字节的值。另一方面,如果我们存储一个 4 字节的值,我们仍然会翻转字节顺序,但只是针对这 4 个字节。以下面的数组为例。

int a[] = {0x12345678, 0x9abcdef0};

这个数组和 8 字节的数字一样,总共占用 8 个字节,看起来非常相似。但是,在内存中,我们不会存储与上面相同的内容,而是以下内容:

0x00: 78 56 34 12

0x04: f0 de bc 9a

请注意这里数组的顺序是如何保留的,并且0x12345678单独的值(前 4 个字节)是小端的。

理解这一点非常重要:我们不会以小端序任意存储任何 8 字节,而是根据它们占用的大小以小端序存储各个值。

作为最后一个示例,采用以下字符数组。

char s[] = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};

正如您可能能够预测的那样,它以以下格式存储。

0x00: 12

0x01: 34

0x02: 56

0x03: 78

0x04: 9a

0x05: bc

0x06: de

0x07: f0

再次,保持数组的顺序。

现在,如果我们将其带回 big-endian,我们可以看到这些示例中的每一个都以相同的方式存储。

0x00: 12 34 56 78 9a bc de f0

这是因为 big-endian 是按照您看到事物的顺序存储的。我建议你自己证明这一点。

那么为什么每个人都会倒退呢?

不管乍一看似乎有悖常理,小端优先于大端的使用是有正当理由的。广泛使用 little-endian 的原因不是因为用户易于理解(您可能已经发现),而是因为计算机易于使用。让我们来看看为什么。我们将使用这个 8 字节的值0x0000000000000042。当我们将它存储在 little-endian 中时,我们有以下内容。

0x00: 42 00 00 00 00 00 00 00

在大端中我们会得到。

0x00: 00 00 00 00 00 00 00 42

现在假设我们要运行以下代码。

// In the case of 64 bit compilers, long long is the same size as long. They are both 8 bytes.unsigned long long x = 0x0000000000000042;unsigned long long * x_p = &x;unsigned int * y_p = (unsigned int *)x_p;unsigned int y = *y_p;

printf("y = %#.8x\\n", y); // prints in hex with \'0x\' and with all leading zeros

我们正在做一些叫做指针向下转换的事情。我们不会改变内存中的任何东西,只是改变处理器从内存中读取的方式。

需要注意的重要一点是x_p和y_p将具有相同的值(它们指向相同的位置)。我们会说它们都指向0x00。

当我们运行它时,根据处理器使用的字节顺序,我们将得到两种截然不同的结果。首先,让我们假设我们使用的是 x86_64 处理器(即小端)。我们得到的正是您期望得到的:y = 0x00000042。这是因为当我们以 4 字节的块重新解释内存并得到以下结果时:

0x00: 42 00 00 00

0x04: 00 00 00 00

现在,当我们只在内存位置抓取 4 个字节时,0x00我们从原始 8 字节值中获得了 4 个最低有效字节。请随意在您的计算机上尝试此操作。

正如您所料,Big-endian 的行为非常不同。想象一下,我们在大端处理器上运行此代码。我们会得到:y = 0x00000000。同样,如果我们以 4 字节的块重新解释内存,我们将看到出现这种情况的原因。

0x00: 00 00 00 00

0x04: 00 00 00 42

该y_p指针(0x00在我们的例子)指向0x00000000。

让代码在 big-endian 中运行是很困难的,因为大多数处理器不是 little-endian 就是bi-endian。但是,您可以通过添加字节交换“模拟”big-endian来更改代码。

unsigned long long x = __builtin_bswap64(0x0000000000000042);unsigned long long * x_p = &x;unsigned int * y_p = (unsigned int *)x_p;unsigned int y = __builtin_bswap32(*y_p);

printf("y = %#.8x\\n", y);

然后你可以在你的电脑上运行它

 

以上是关于什么是大小端的主要内容,如果未能解决你的问题,请参考以下文章

关于大小端转换整理总结(包含原始方式Qt方式)

大小端模式

CPU的大小端模式

大小端存储--数据在内存中的存储方式

大小端存储在哪一章

大小端存储