大端和小端的一点困惑

3

我正在阅读这个网站http://www.geeksforgeeks.org/little-and-big-endian-mystery/上关于小端和大端表示法的内容。

假设我们有一个数字0x01234567,那么在小端中它被存储为(67)(45)(23)(01),而在大端中则被存储为(01)(23)(45)(67)。

char *s= "ABCDEF"
int *p = (int *)s;
printf("%d",*(p+1)); // prints 17475 (value of DC)

在上面的代码中,看到打印出来的值后,似乎字符串以(BA)(DC)(FE)的形式存储。
为什么它不像第一个例子那样从LSB到MSB以(EF)(CD)(AB)的方式存储呢?我认为字节序意味着多字节内的字节顺序。因此,应该按照第二种情况的“整个2字节”进行排序,而不是在这两个字节内进行排序,对吗?

在上面的代码中看到打印出来的值后,是什么值呢?在我的小端机器上,给定的代码打印出 17989 (十六进制: 0x4645),这对我来说似乎非常正常。 - jwodder
2
这对我来说看起来像是未定义行为。s 指向 6 个字节。你将 p 赋值为 s,但当你打印时却使用了 p+1。假设你的 int 是 4 个字节,这将把 p 指向 'E'。下一个字节是 'F',然后接下来的两个字节超出了你分配的空间。但撇开这些不谈,这看起来很好,我的小端打印输出是 0x25004645。0x45 是 'E',0x46 是 'F',而 0x00 和 0x25 则是无人区。 - yano
1
@yano,我的编译器将CD视为“DC”。我有2字节的整数。请看我的编辑。 - Sagar P
1
好的,使用 2 字节的 int(在我的机器上是 short),忘记 UB(未定义行为),看起来仍然不错。我的输出现在是 0x4443,其中0x43是 'C',0x44是 'D'。我怀疑你把字符串的 ASCII 字符与十六进制值混淆了?你字符串中的每个字符对应一个字节,可以用 2 位十六进制数表示。使用 printf 格式说明符 "%x" 以十六进制打印。17475 确实是 0x4443,这是小端机器所期望的结果。 - yano
3
"ABCDEF"和0xABCDEF是非常不同的... - Déjà vu
显示剩余3条评论
4个回答

12

使用2字节的int,这是您在内存中拥有的内容

memAddr  |  0  |  1  |  2  |  3  |  4  |  5  |  6   |
data     | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | '\0' |
            ^ s points here
                        ^ p+1 points here

现在,看起来你正在使用ASCII编码,所以这实际上是你在内存中真正拥有的

memAddr  |  0   |  1   |  2   |  3   |  4   |  5   |  6   |
data     | 0x41 | 0x42 | 0x43 | 0x44 | 0x45 | 0x46 | 0x00 |
            ^ s points here
                          ^ p+1 points here

对于小端机器而言,这意味着多字节类型的最低有效字节排在前面。单个字节的char没有大小端的概念。ASCII字符串只是一串chars,没有大小端之分。int为2个字节。所以对于从内存位置2开始的int,该字节是最不重要的,地址3处的字节是最重要的。这意味着这里读取的数字通常按人们阅读数字的方式来看是0x4443(十进制下为17475,“DC”作为ASCII字符串),因为内存位置3中的0x44比内存位置2中的0x43更重要。当然,在大端模式下,这将被反转,并且数字将是0x4344(十进制下为17220,“CD”作为ASCII字符串)。

回应您的评论...c字符串是一个以NUL结尾的char数组,这是绝对正确的。字节序仅适用于原始类型,如short、int、long、long long等(“原始类型”可能不正确,知道的人可以纠正我)。数组只是一段连续内存,其中一个或多个类型直接相邻存储,按顺序存储。整个数组没有字节序的概念,但是字节序确实适用于数组中各个元素的原始类型。假设你有以下内容,假设是2字节的int

int array[3];  // with 2 byte ints, this occupies 6 contiguous bytes in memory
array[0] = 0x1234;
array[1] = 0x5678;
array[2] = 0x9abc;

这就是内存的样子:无论是大端还是小端机器,它的样子都是一样的。

memAddr   |    0-1   |    2-3   |    4-5   |
data      | array[0] | array[1] | array[2] |

注意,对于数组的元素,不存在字节序的概念。无论元素是什么,都是如此。元素可以是原始类型、结构体等任何类型。数组中的第一个元素始终位于array[0]
但是,现在,如果我们查看实际存储在数组中的内容,这就涉及到字节序的问题。对于小端机器,内存将如下所示:
memAddr   |  0   |  1   |  2   |  3   |  4   |  5   |
data      | 0x34 | 0x12 | 0x78 | 0x56 | 0xbc | 0x9a |
             ^______^      ^______^      ^______^
             array[0]      array[1]      array[2]

最不重要的字节排在最前面。大端机器看起来是这样的:

memAddr   |  0   |  1   |  2   |  3   |  4   |  5   |
data      | 0x12 | 0x34 | 0x56 | 0x78 | 0x9a | 0xbc |
             ^______^      ^______^      ^______^
             array[0]      array[1]      array[2]

注意数组中每个元素的内容取决于字节序(因为它是原始类型的数组...如果它是结构体数组,结构体成员就不会受到某种字节序反转的影响,字节序仅适用于原始类型)。但是,无论在大端还是小端机器上,数组元素的顺序仍然相同。
回到你的字符串,字符串只是一个以NUL结尾的字符数组。char是单个字节,所以只有一种排序方式。考虑以下代码:
char word[] = "hey";

这是您在内存中拥有的内容:

memAddr   |    0    |    1    |    2    |    3    |
data      | word[0] | word[1] | word[2] | word[3] |
                  equals NUL terminator '\0' ^

在这种情况下,word数组的每个元素都是一个单字节,而且只有一种方式来对单个项目进行排序,因此无论在小端或大端机器上,这就是你在内存中拥有的内容:

memAddr   |  0   |  1   |  2   |  3   |
data      | 0x68 | 0x65 | 0x79 | 0x00 |

字节序仅适用于多字节原始类型。 我强烈建议在调试器中四处查看以了解其实际效果。 所有流行的IDE都有内存查看窗口,或者使用 gdb 可以打印内存。 在 gdb 中,您可以按字节、半字(2个字节)、字(4个字节)、巨型字(8个字节)等方式打印内存。 在小端机器上,如果将字符串按字节打印出来,您将按顺序看到字母。 按半字打印,您会看到每2个字母“反转”,按字打印,每4个字母“反转”等等。 在大端机器上,所有内容都将按相同的“可读”顺序打印出来。


谢谢你的回答。字符串是字节或多字节的数组,对吧?那么为什么字符(单字节)不会以相反的顺序存储,因为它是多字节字符串的一部分呢? - Sagar P
1
最好的关于字节序的详细描述,以及大端和小端区别的阐述。出色的工作。 - young chisango

3

2022 编辑

处理器不知道它正在处理什么类型的数据,文本还是数字。在内存中,所有东西都是数字。

(特殊的CPU指令处理浮点数,期望4/8个字节符合某些标准,但无论如何,在内存中它们只是8位字节,其值从0到255,就像其他所有东西一样)

通常情况下

the letter 'A' is represented by the hex number 0x41 (65 decimal)
...     
the letter 'F' is represented by the hex number 0x46 (70 decimal) 

文本"ABCDEF"在内存中显示为(十六进制),(在C语言中编译器在'F'之后添加一个字节0)

----- addresses ----->
|41|42|43|44|45|46|00|
----------------------

在一个2字节的小端系统中,在变量 i 中读取p+1位置(您的示例)的整数。
---- addresses --->
|41|42|43|44|45|46|
|p + 0|p + 1|p + 2|        
-------------------

由于这是一个小端系统,i 的值为

 0x4443 (17475 in decimal, the value you saw)

读取的第一个字节是一个int的最低有效字节(0x43),下一个字节是最高有效字节(MSB, 0x44)。

请注意,在大端系统上,它将是

 0x4344 (17220 in decimal)

现在可能您想将数字 0xABCDEF(十六进制,11259375 十进制)存储在内存中。我们至少需要一个 32 位整数才能将该值存储在变量中;假设您的编译器 long 类型拥有 32 位。
long l = 0xABCDEF;

在内存(小端)中,数字的存储方式如下:

------------>
|EF|CD|AB|00|
-------------

请注意,这里还有一个“尾随”的0,但在这种情况下,它是32位上该数字的MSB为0。
在这种情况下,您会发现您期望的(EF)(CD)(AB),因为编译器将代码字0xABCDEF视为数字。 在另一种情况下,编译器按其原样处理文本“ABCDEF”,即一系列字符。

2017原始回答

似乎在字符串和字符之间存在一些混淆。

1)  "ABCDEF"

数字11,259,375在十六进制表示为

2)  0xABCDEF

在第一种情况下,每个字母占用一个完整的字节。
在第二种情况下,我们有六个十六进制数字;一个十六进制数字占用4位,因此一个字节需要两个数字。
关于字节序,在以下情况下:
  1. 字符'A',然后是'B'等依次写入内存。 'A'是0x41,'B'是0x42... 在这种情况下
  2. 这是一个多字节整数,其字节顺序取决于架构。假设数字是4个字节,则大端架构将以以下方式存储在内存中(十六进制):00 AB CD EF; 小端架构将按以下顺序存储:EF CD AB 00
大端序
A  B  C  D  E  F
41 42 43 44 45 46   [ text ]
00 AB CD EF         [ integer ] 
----(addresses)---->

小端字节序

----(addresses)---->
A  B  C  D  E  F
41 42 43 44 45 46   [ text ]
EF CD AB 00         [ integer ]

在您的情况下
char *s= "ABCDEF";     // text
int *p = (int *)s;     //
printf("%d",*(p+1));   // *(p+1) is p[1]

自从您的实现中sizeof(int) == 2,打印出的数字(17475)是0x4443,或者说是' DC '(字符),其中0x44('D')作为MSB,0x43('C')作为LSB,表明您的架构是小端序。
在内存中顺序地写入一串字符,并将其中几个作为int读取,会得到一个取决于字节序的数字。是的,在这种情况下,字节序很重要

谢谢你的回答。字符串是字节或多字节的数组,对吧?那么为什么字符(单字节)不会以相反的顺序存储,因为它是多字节字符串的一部分呢? - Sagar P
字符串由字符组成,有时数量很多,这些字符按顺序存储 - 没有其他可供选择的方式,这样做甚至可能会有问题。 整数(数字)根据架构进行存储是因为CPU /寄存器和内存之间的映射,这是制造商做出的选择 - 请参见每种架构的优势 - Déjà vu
好的,明白了!谢谢!! - Sagar P
应该说一个重要的观点:“数字”是固定大小的,而字符串则不是(远非如此!)。这是允许对int、float等进行特殊处理的另一个原因。 - Déjà vu
所以在内存中,它实际上是按照ABCDEF(仅按此顺序)作为char数组存储的。但是,仅仅因为我们使用int指针来访问char数组,当解引用这2个字节时,会被不同地解释对吧? - Zephyr

1
当谈到像 char const 数组中存储字节时,字节顺序并不起作用,该数组由指针 s 指向。如果你查看 *s 处的内存,你会发现字节 'a'、'b'、'c'……,然而在小端系统上,当将其解释为 int 时,它将被解释为 "DCBA"。
请记住,每个 char 已经是一个字节。如果你有 char const * s = "0xfedcab09"; 并在小端系统上使用 *(int const *)s 进行 printf("%d"),那么它将打印出十进制下 0x9abcdef 的结果。

0
这里出现的混淆是由于符号表示法造成的。
字符串“ABCDEF”可以有多种解释(和存储)方式。
在字符串中,每个字母占用一个完整的字节(char)。
注:本文中的“字符串”指的是编程语言中的数据类型,而非一般意义上的字符串。
char s[] = { 'A', 'B', 'C', 'D', 'E', 'F', 0 };

然而,十六进制表示法中的数字ABCDEF是不同的,每个数字('0'..'9'和'A'..'F')仅代表四位或半个字节。因此,数字0xABCDEF是字节序列。
0xAB 0xCD 0xEF

这里就涉及到了字节序的问题:

  • 小端序:最低有效字节在前
    int x = { 0xEF, 0xCD, 0xAB };
  • 大端序:最高有效字节在前
    int x = { 0xAB, 0xCD, 0xEF }
  • 混合序:<其他随机顺序>
    int x = { 0xEF, 0x00, 0xCD, 0xAB }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接