对big endian和little endian差异的按位运算

时间:2016-04-23 15:20:30

标签: c bit-manipulation

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

一些背景资料: A / 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。

我在这里缺少什么?不应该对big endian的工作方式一样吗?

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

2 个答案:

答案 0 :(得分:1)

x86是小端。小端的二进制数字是

data Edge v = Edge {source :: v, target :: v}
          deriving (Show,Eq,Ord)

data Graph v = Graph {nodes :: Set v, edges :: Set (Edge v)}
           deriving Show


instance Arbitrary v => Arbitrary (Edge v) where
    arbitrary = do s <- arbitrary
                   t <- arbitrary
                   return $ Edge {source = s, target = t}

instance (Ord v, Arbitrary v) => Arbitrary (Graph v) where
    arbitrary = aux `suchThat` validGraph
        where aux = do lNodes <- arbitrary
                       lEdges <- arbitrary
                       return $ Graph {nodes = fromList lNodes, edges = fromList lEdges}

如果您将此位向左移9位,则变为......

|10000000|00000000|00000000|00000000

大多数人在考虑二元时认为数字1表示如下,有些人|00000000|01000000|00000000|00000000 这是大端,但它不是......

think

|00000000|00000000|00000000|00000001 的大端表示是

1

试试此代码

|00000000|00000000|00000000|10000000

这是输出

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

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);
  return 0;
}

答案 1 :(得分:1)

Big Endian和Little Endian的问题并不为机器所知。

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

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

出于这个原因,总是使用局部字节序假设执行位移。

您无法在Little Endian计算机上正确应用位移到Big Endian数字。

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

这就是为什么@Harry的答案非常酷,它会打印出每一个字,避开问题。

维基百科有一个article about Endianness,其中包含更多详细信息。

应该注意,Endianness实际上是指机器将其字节存储在内存中的方式。

例如,如果数字是字符串,则Endianness会引用一个问题:首先出现哪个“字母”(字节)?

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

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

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