【问题标题】:Bitwise operation on big endian and little endian differences大端和小端差异的按位运算
【发布时间】:2016-04-23 15:20:30
【问题描述】:

我正在对 IP 地址空间的前缀“子级”进行排序。例如,8.8.8.0/24 是 IP 地址空间中 8.8.8.0/23 的子级。我很困惑为什么以下两个操作在我的 x86 little endian 系统上提供不同的结果

一点背景资料: /24 表示 32 位 IPv4 地址的前 24 位已“定义”。这意味着 8.8.8.0/24 包含 8.8.8.0 - 8.8.8.255。同样,对于未定义的每一位,地址空间量都会增加一倍。 8.8.8.0/23 只定义了前 23 位,因此实际地址空间从 8.8.8.0 到 8.8.9.255,或者是 /24 大小的两倍。

现在我的困惑在于以下位移

inet_addr("8.8.8.0") << (32 - 23) produces 269488128
inet_addr("8.8.9.0") << (32 - 23) produces 303042560

inet_addr 产生一个大端序号。但是,将其转换为小端时 -

htonl(inet_addr("8.8.8.0")) >> 9 produces 263172
htonl(inet_addr("8.8.9.0")) >> 9 produces 263172

这是预期的结果。删除最后 9 位意味着理论上 8.8.9.0 将等于 8.8.8.0。

我在这里缺少什么?大端不应该一样吗?

编辑:不是重复的,因为我确实了解字节顺序如何影响数字存储方式的差异,但我显然缺少这些按位运算符的一些东西。这个问题更多地与按位而不是字节序有关 - 字节序只是为了培养一个例子

【问题讨论】:

  • 这 4 个输出中哪一个是出乎意料的?您期望的价值是多少?
  • 前两个输出是意外的。我希望前 2 个输出相等,就像第三个和第四个输出相等
  • htonl 不会反转位;它反转 8 位单位。在第一种情况下,不同的位是左起第 16 位;在第二种情况下,不同的位是右边的第 9 位
  • 这可以解释原因,谢谢!

标签: c bit-manipulation


【解决方案1】:

x86 是小端。 little endian 中的二进制数 1 是

|10000000|00000000|00000000|00000000

如果你把这个左移 9 位,它就变成了......

|00000000|01000000|00000000|00000000

在小端机器中,0xDEADBEEF 打印为从低地址到高地址的一系列字节实际上会打印 EFBEADDE,请参阅

https://www.codeproject.com/Articles/4804/Basic-concepts-on-Endianness

https://www.gnu-pascal.de/gpc/Endianness.html.

大多数人在思考二进制时认为数字 1 表示如下(包括我),有些人 think 这是大端,但不是......

|00000000|00000000|00000000|00000001

在下面的代码中,我以小端序打印了 0xDEADBEEF,因为我的机器是 x86,并且我使用了 htonl 函数将其转换为网络字节顺序。注意网络字节顺序被定义为Big Endian。

所以当我打印出 1 的大端值时,即htonl(1)1 的大端表示是

|00000000|00000000|00000000|10000000

试试这个代码

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>

void print_deadbeef(void *p, size_t bytes) {
  size_t i = 0;
  for (i = 0; i < bytes; ++i) {
    printf("%02X", ((unsigned char*)p)[i]);
  }
  printf("\n");
}

void print_bin(uint64_t num, size_t bytes) {
  int i = 0;
  for(i = bytes * 8; i > 0; i--) {
    (i % 8 == 0) ? printf("|") : 1;
    (num & 1)    ? printf("1") : printf("0");
    num >>= 1;
  }
  printf("\n");
}

int main(void) {
  in_addr_t left    = inet_addr("8.8.8.0");
  in_addr_t right   = inet_addr("8.8.9.0");
  in_addr_t left_h    = htonl(left);
  in_addr_t right_h   = htonl(right);
  in_addr_t left_s  = left  << 9;
  in_addr_t right_s = right >> 9;
  assert(left  != right);

  printf("left != right\n");
  print_bin(left, 4);
  print_bin(right, 4);
  printf("Big Endian if on x86\n");
  print_bin(left_s, 4);
  print_bin(right_s, 4);
  printf("Little Endian if on x86\n");
  print_bin(left_h, 4);
  print_bin(right_h, 4);

  printf("\n\nSome notes\n\n");

  printf("0xDEADBEEF printed on a little endian machine\n");
  uint32_t deadbeef = 0xDEADBEEF;
  print_deadbeef(&deadbeef, 4);

  uint32_t deadbeefBig = htonl(deadbeef);
  printf("\n0xDEADBEEF printed in network byte order (big endian)\n");
  print_deadbeef(&deadbeefBig, 4);

  printf("\n1 printed on a little endian machine\n");
  print_bin(1, 4);
  printf("\nhtonl(1) ie network byte order (big endian) on a little endian machine\n");
  print_bin(htonl(1), 4);

  return 0;
}

这是输出

left != right
|00010000|00010000|00010000|00000000
|00010000|00010000|10010000|00000000
Big Endian if on x86
|00000000|00001000|00001000|00001000
|00100001|00100000|00000000|00000000
Little Endian if on x86
|00000000|00010000|00010000|00010000
|00000000|10010000|00010000|00010000


Some notes

0xDEADBEEF printed on a little endian machine
EFBEADDE

0xDEADBEEF printed in network byte order (big endian)
DEADBEEF

1 printed on a little endian machine
|10000000|00000000|00000000|00000000

htonl(1) ie network byte order on a little endian machine
|00000000|00000000|00000000|10000000

【讨论】:

  • 如果反对者能解释原因,那真的很有帮助。我认为代码准确地显示了正在发生的事情。
  • Harry 进一步深入了解为什么会这样。
  • 我也认为这是一个很好的答案(代码和输出实际上说明了一切),尽管我会添加一个带有解释的答案。
  • 我会增加一些深度,看看它是否有帮助。当看起来 OP 不需要手握时,我通常只发布一些代码来演示正在发生的事情。
  • @Harry ,我认为您的更新答案提供了一些错误信息......据我了解,大端与小端排序仅指字节(而不是位)。所以1,在一个字节中,总是0b00000001,无论您使用哪种字节序系统。字节序存储是指字节的地址空间(内存映射),而不是位。
【解决方案2】:

机器并不真正知道大端和小端的问题。

C 中的类型不包含此类信息,因为这是硬件问题,而不是与类型相关的问题。

机器假定所有的多字节数字都按照它的本地字节序排序(在 x86 上,这通常是小字节序)。

因此,总是使用本地字节序假设执行位移。

您无法在 Little Endian 机器上正确地对 Big Endian 数应用位移。

您甚至无法在 Little Endian 机器上将 Big Endian 数字打印到屏幕上而不会得到有趣的结果。

这就是@Harry 的回答如此酷的原因,它会打印出每一位,从而规避问题。

维基百科有一个article about Endianness 提供更多详细信息。

需要注意的是,字节序实际上是指机器在内存中存储字节的方式。

例如,如果数字是字符串,则字节序将指以下问题:哪个“字母”(字节)会先出现?

有些机器会存储“Hello”,有些会存储“olleH”(仅对于数字,在实际字符串中,字节总是正确排序的)。

请注意,虽然字节的顺序是颠倒的,但每个字节的所有位都以相同的方式排序,所以每个字节都保留了它的值。

当发生位移时,它总是根据机器的字节顺序系统发生,因为这是它的 CPU 和内存存储的设计方式。

【讨论】:

    猜你喜欢
    • 2010-11-05
    • 1970-01-01
    • 2011-06-23
    • 2016-12-19
    • 2011-12-24
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 2011-02-04
    相关资源
    最近更新 更多