大端和小端差异的位运算
Bitwise operation on big endian and little endian differences
我正在为 IP 地址 space 排序 "children" 个前缀。例如8.8.8.0/24就是IP地址space中8.8.8.0/23的child。我很困惑为什么以下两个操作在我的 x86 little endian 系统上提供不同的结果
一些背景信息:
/24 表示 32 位 IPv4 地址的前 24 位是 "defined"。这意味着 8.8.8.0/24 包含 8.8.8.0 - 8.8.8.255。同样,对于未定义的每一位,地址数量 space 都会加倍。 8.8.8.0/23 只定义了前 23 位,因此实际地址 space 从 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。
我在这里错过了什么?它不应该同样适用于大端吗?
编辑:不是重复的,因为我确实了解字节顺序如何影响数字存储方式的差异,但我显然遗漏了这些按位运算符的某些内容。这个问题更多地与按位有关,而不是字节序——字节序只是为了培养一个例子
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
Big Endian 和 Little Endian 的问题并不是机器真正知道的。
C 中的类型不包含此类信息,因为它是硬件问题,与类型无关。
机器假定所有多字节数字都根据其本地字节序排序(在 x86 上,这通常是小字节序)。
因此,总是使用本地字节序假设执行位移位。
您无法在 Little Endian 机器上正确地对 Big Endian 数字应用位移。
即使在小端机上将大端数打印到屏幕上,也无法得到有趣的结果。
这就是为什么@Harry 的回答很酷,它打印了每一位,绕过了这个问题。
维基百科 article about Endianness 有更多详细信息。
需要注意的是,字节顺序实际上是指机器在内存中存储字节的方式。
例如,如果数字是字符串,字节序指的是这样的问题:哪个"letter"(字节)先到?
有些机器会存储 "Hello",有些机器会存储 "olleH"(仅对于数字,在实际字符串中,字节总是正确排序)。
请注意,尽管字节的顺序是颠倒的,但每个字节的所有位都以相同的方式排序,因此每个字节都保留其值。
当发生位移时,它总是根据机器的字节排序系统发生,因为这就是 CPU 和内存存储的设计方式。
我正在为 IP 地址 space 排序 "children" 个前缀。例如8.8.8.0/24就是IP地址space中8.8.8.0/23的child。我很困惑为什么以下两个操作在我的 x86 little endian 系统上提供不同的结果
一些背景信息: /24 表示 32 位 IPv4 地址的前 24 位是 "defined"。这意味着 8.8.8.0/24 包含 8.8.8.0 - 8.8.8.255。同样,对于未定义的每一位,地址数量 space 都会加倍。 8.8.8.0/23 只定义了前 23 位,因此实际地址 space 从 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。
我在这里错过了什么?它不应该同样适用于大端吗?
编辑:不是重复的,因为我确实了解字节顺序如何影响数字存储方式的差异,但我显然遗漏了这些按位运算符的某些内容。这个问题更多地与按位有关,而不是字节序——字节序只是为了培养一个例子
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
Big Endian 和 Little Endian 的问题并不是机器真正知道的。
C 中的类型不包含此类信息,因为它是硬件问题,与类型无关。
机器假定所有多字节数字都根据其本地字节序排序(在 x86 上,这通常是小字节序)。
因此,总是使用本地字节序假设执行位移位。
您无法在 Little Endian 机器上正确地对 Big Endian 数字应用位移。
即使在小端机上将大端数打印到屏幕上,也无法得到有趣的结果。
这就是为什么@Harry 的回答很酷,它打印了每一位,绕过了这个问题。
维基百科 article about Endianness 有更多详细信息。
需要注意的是,字节顺序实际上是指机器在内存中存储字节的方式。
例如,如果数字是字符串,字节序指的是这样的问题:哪个"letter"(字节)先到?
有些机器会存储 "Hello",有些机器会存储 "olleH"(仅对于数字,在实际字符串中,字节总是正确排序)。
请注意,尽管字节的顺序是颠倒的,但每个字节的所有位都以相同的方式排序,因此每个字节都保留其值。
当发生位移时,它总是根据机器的字节排序系统发生,因为这就是 CPU 和内存存储的设计方式。