【问题标题】:Big Endian and Little endian little confusionBig Endian 和 Little Endian 的小混淆
【发布时间】:2018-05-15 04:03:36
【问题描述】:

我正在阅读来自该站点 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 个字节”的,就像第二种情况一样,而不是在这 2 个字节内,对吧?

【问题讨论】:

  • "看到上面代码中的打印值后,"——什么打印值?在我的 little-endian 机器上,给定的代码打印出 17989(十六进制:0x4645),这对我来说似乎很正常。
  • 这在我看来像 UB。 s 指向 6 个字节。您将p 等同于s,但是当您打印时,您会使用p+1。假设您有 4 字节 ints,这将指向 p'E'。下一个字节是'F',然后接下来的 2 个字节超出了您分配的空间。但除此之外,对我来说看起来不错,我的小端打印输出是 0x25004645。 0x45是'E',0x46是'F',0x00和0x25是无人区。
  • @yano,我的编译器将 CD 视为“DC”。我有 2 个字节的整数。查看我的编辑。
  • 好的,有 2 字节 int's(shorts 在我的机器上),忘记 UB,看起来仍然不错。我现在的打印输出是 0x4443,其中 0x43 是 'C',0x44 是 'D'。我怀疑您将字符串 ASCII 字符与十六进制值混淆了?字符串中的每个字符对应一个字节,可以用 2 位十六进制数表示。使用printf 格式说明符"%x" 以十六进制打印。 17475 确实是 0x4443,这是小端机器所期望的。
  • "ABCDEF"0xABCDEF 非常不同...

标签: c pointers endianness


【解决方案1】:

在谈到将字节存储在s 指向的 char const 数组中时,字节序不会发挥作用。如果您检查*s 的内存,您会发现字节'a''b''c' ...,当在小端系统上解释为int 时,它会被解释为"DCBA" .

请记住,每个 char 已经是一个字节,如果您有 char const * s = "0xfedcab09"; 并且您在小端系统上执行了 printf("%d", *(int const *)s);,那么它将打印为 0x9abcdef 以十进制形式出现的任何内容。

【讨论】:

    【解决方案2】:

    使用 2 字节 ints,这就是你在内存中的内容

    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 的字符串。这没有字节序。您的 ints 是 2 个字节。因此,对于从内存位置 2 开始的int,该字节是最不重要的,而地址 3 处的字节是最重要的。这意味着这里的数字,按照人们通常阅读数字的方式读取,是 0x4443(以 10 为基数的 17475,“DC”作为 ASCII 字符串),因为内存位置 3 中的 0x44 比内存位置 2 中的 0x43 更重要。对于大端,当然,这将被颠倒过来,数字将是 0x4344(以 10 为基数的 17220,“CD”作为 ASCII 字符串)。

    编辑:

    解决您的评论...c 字符串是NUL 终止的chars 数组,这绝对正确。 Endianess 仅适用于原始类型 short, int, long, long long 等(“原始类型”可能是不正确的命名法,知道的人可以纠正我)。数组只是连续内存的一部分,其中一种或多种类型直接相邻出现,按顺序存储。整个数组没有字节序的概念,但是字节序确实适用于数组中各个元素的原始类型。假设您有以下内容,假设 2 字节 ints:

    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] |
    

    请注意,数组元素没有字节序的概念。无论元素是什么,这都是正确的。元素可以是原始类型,structs,任何东西。数组中的第一个元素始终位于 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]
    

    请注意,数组中每个元素的 内容 都服从字节顺序(因为它是原始类型数组。如果它是 structs 的数组,struct 成员将不会t 受到某种字节序反转的影响,字节序仅适用于原语)。但是无论在大端还是小端机器上,数组元素的顺序还是一样的。

    回到您的字符串,字符串只是一个以NUL 结尾的字符数组。 chars 是单字节,所以只有一种方法可以订购它们。考虑代码:

    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 |
    

    Endianess 仅适用于多字节原始类型。我强烈建议在调试器中四处寻找,以在现场看到这一点。所有流行的 IDE 都有内存查看窗口,或者使用gdb,您可以使用print out memory。在gdb 中,您可以将内存打印为字节、半字(2 个字节)、字(4 个字节)、巨型字(8 个字节)等。在小端机器上,如果您将字符串打印为字节,您将按顺序查看字母。打印为半字,你会看到每 2 个字母“反转”,打印为单词,每 4 个字母“反转”,等等。在大端机器上,它都会以相同的“可读”顺序打印出来。

    【讨论】:

    • 感谢您的回答。字符串是字节数组还是多字节数组?那么为什么字符(单字节)不反向存储,因为它是多字节字符串的一部分?
    • 对字节序的最佳描述和详尽描述,以及大字节序和小字节序的区别。伟大的工作
    【解决方案3】:

    这里出现的混淆是由于符号

    字符串“ABCDEF”可以以多种方式解释(和存储)。

    字符串中,每个字母占用一个完整的字节 (char)。

    char s[] = { 'A', 'B', 'C', 'D', 'E', 'F', 0 };
    

    但是,数字 ABCDEF的十六进制表示是不同的,每个数字('0'..'9'和'A'..'F')只代表四位,或半个字节。因此,数字0xABCDEF是字节序列

    0xAB 0xCD 0xEF
    

    这就是 endianness 成为问题的地方:

    • 小端:最低有效字节在前
      int x = { 0xEF, 0xCD, 0xAB };
    • Big Endian:最高有效字节在前
      int x = { 0xAB, 0xCD, 0xEF }
    • 混合字节序:
      int x = { 0xEF, 0x00, 0xCD, 0xAB }

    【讨论】:

      【解决方案4】:

      字符串之间好像有点混淆

      1)  "ABCDEF"
      

      十六进制表示的数字11,259,375是

      2)  0xABCDEF
      

      在第一种情况下,每个字母占用一个完整的字节。
      在第二种情况下,我们有六个十六进制数字;一个十六进制数字占 4 位,所以一个字节需要两个数字。

      字节序明智的,以防万一
      1)字符'A',然后'B'等..按顺序写入内存。 'A' 是 0x41,'B' 是 0x42...以防万一
      2)这是一个多字节整数,其字节顺序取决于架构。假设数字是 4 个字节,大端弧将存储在内存中(十六进制) 00 AB CD EF ; little-endian 将按以下顺序存储: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 表明您的架构是 little-endian。

      在内存中(按顺序)写入一串字符并以int 的形式读取其中的几个字符会给出一个取决于字节序的数字。是的,在这种情况下,字节顺序很重要

      【讨论】:

      • 感谢您的回答。字符串是字节数组还是多字节数组?那么为什么字符(单字节)不反向存储,因为它是多字节字符串的一部分?
      • 字符串由字符组成,有时很多,并且字符是按顺序存储的——没有理由不这样做,这甚至会产生问题。基于架构存储整数(数字)的原因是由于 CPU / 寄存器和内存之间的映射,这是由于制造商做出的选择 - 请参阅advantages of each arc
      • 是的,明白了!谢谢!!
      • 应该说重要的一点:“数字”是固定大小的,字符串不是(远非如此!)。允许对 int、float、...进行特殊处理的另一个原因
      • 因此在内存中它实际上存储为 ABCDEF(仅按此顺序),因为它是一个字符数组。但是仅仅因为我们使用一个 int 指针来访问 char 数组,解引用时的 2 个字节得到的解释不同,对吧?
      猜你喜欢
      • 2019-06-30
      • 2020-12-01
      • 2022-06-10
      • 2012-10-09
      • 2010-10-16
      • 2019-03-13
      • 2022-12-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多