C语言 对大端和小端差异的按位操作

eagi6jfj  于 2023-02-03  发布在  其他
关注(0)|答案(3)|浏览(144)

我正在对IP地址空间的前缀“子项”进行排序。例如,8.8.8.0/24是8.8.8.0/23IP地址空间中www.example.com的子项。我不明白为什么以下两个操作在我的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理论上等于www.example.com。
我错过了什么?big endian不也应该一样吗?
编辑:不重复,因为我确实理解字节序对数字存储方式的影响,但我显然忽略了这些位操作符的某些方面。问题更多地与位操作符有关,而不是字节序-字节序只是为了提供一个示例

kx5bkwkv

kx5bkwkv1#

x86是小端字节序。小端字节序的二进制数字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

在下面的代码中,我用littleendian输出了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
kxkpmulp

kxkpmulp2#

大字节序和小字节序的问题机器并不真正知道。
C中的类型不包含这样的信息,因为这是硬件问题,而不是类型相关的问题。
机器假设所有的多字节数都是按照本地字节序排序的(在x86上,通常是小字节序)。
因此,总是使用本地字节序假设来执行位移位。
您无法在小端计算机上正确地将位移位应用于大端数字。
你甚至不能在小端机器上打印一个大端数字到屏幕上而不得到一个有趣的结果。
这就是为什么“哈里的回答如此酷,它打印出每一个比特,绕过了问题。”
维基百科有一个article about Endianness,里面有更多的细节。
应该注意的是,字节序实际上是指机器在内存中存储字节的方式。
例如,如果数字是字符串,字节序将引用以下问题:哪个“字母”(字节)会先出现?
有些机器存储“Hello”,有些存储“olleH”(仅对于数字,在实际字符串中,字节总是正确排序的)。
请注意,虽然字节的顺序颠倒了,但每个字节的所有位都以相同的方式排序,因此每个字节都保留了它的值。
当发生位移位时,它总是根据机器的字节排序系统发生,因为这是它的CPU和内存存储的设计方式。

qv7cva1a

qv7cva1a3#

公认的答案提供了一个很好的示例程序。然而,我认为这个示例有点误导。
1的小端字节序位串打印为:

10000000|00000000|00000000|00000000

我在我的x86 pc上运行了这段代码,我认为结果是可靠的,但这并不意味着1的值就像上面打印的那样存储在little-endian机器中。
根据print_bin的代码,num每次右移一位,并打印最低有效位。此外,right shift运算符总是从most significant bit (MSB)移到least significant bit (LSB)
最后,无论位顺序如何,print_bin(1, 4)的结果总是与1的人工写入位表示相反,即:

00000000|00000000|00000000|00000001

例如,位串可以是:

byte significance increase -->
  byte
/-------\
00000001|00000000|00000000|00000000
  |
 bit
<-- bit significance increase

在这个例子中,位顺序与字节顺序不同,但是print_bin(1,4)的结果是相同的。

**换句话说,在little-endian机器中,打印的位串并不一定意味着反转位顺序。**我在this blog中进一步讨论了这一点。

相关问题