C中无符号64位数字字节序转换问题
Problem of converting byte order for unsigned 64-bit number in C
我正在玩一些 endian/big 字节序转换,发现一些有点令人困惑但也很有趣的东西。
在第一个例子中,uint32_t
类型使用位移位转换字节顺序没有问题。它基本上将 uint32_t
整数转换为 uint8_t
的数组,并尝试访问每个字节和位移位。
示例 #1:
uint32_t htonl(uint32_t x)
{
uint8_t *s = (uint8_t*)&x;
return (uint32_t)(s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3]);
}
但是,如果我尝试在下面的 uint64_t
上做类似的事情,编译器会抛出一条关于“s[0] 宽度小于 56 位”的警告,如下面的示例 #2 所示。
示例 #2:
uint64_t htonl(uint64_t x)
{
uint8_t *s = (uint8_t*)&x;
return (uint64_t)(s[0] << 56 ......);
}
为了让它工作,我必须将每个字节提取到一个 uint64_t
中,这样我就可以像下面的示例 #3 一样进行位移而不会出现任何错误。
示例 #3:
uint64_t htonll2(uint64_t x)
{
uint64_t byte1 = x & 0xff00000000000000;
uint64_t byte2 = x & 0x00ff000000000000;
uint64_t byte3 = x & 0x0000ff0000000000;
uint64_t byte4 = x & 0x000000ff00000000;
uint64_t byte5 = x & 0x00000000ff000000;
uint64_t byte6 = x & 0x0000000000ff0000;
uint64_t byte7 = x & 0x000000000000ff00;
uint64_t byte8 = x & 0x00000000000000ff;
return (uint64_t)(byte1 >> 56 | byte2 >> 40 | byte3 >> 24 | byte4 >> 8 |
byte5 << 8 | byte6 << 24 | byte7 << 40 | byte8 << 56);
}
我对 Example #1
和 Example #2
有点困惑,据我所知,s[i]
都是 uint8_t
大小,但如果只是移动 32 位或更少位完全没有问题,但移动 56 位时就会出现问题。我在 Ubuntu 上使用 GCC 8.3.0 运行 这个程序。
在这种情况下,编译器是否将 s[i]
隐式转换为 32 位数字? sizeof(s[0])
当我向其中添加调试消息时为 1。
类型小于 int
的值在表达式中使用时 提升 为 int
。假设 int
在您的平台上是 32 位的,这在转换 32 位值时适用于 大多数 情况。如果您将 1 位移入符号位,它将不起作用。
在 64 位的情况下,这意味着您试图移动一个超过其位长度的值,这是未定义的行为。
在这两种情况下,您都需要将每个字节都转换为 uint64_t
,以允许轮班正常工作。
s[0]
表达式有一个 8 位宽的整数类型,当被移位运算符操作时,它被提升为一个 32 位无符号整数——所以第一个例子中的 s[0] << 24
有效好的,因为移动 24 不会超过 uint
长度。
OTOH 移动 56 位会将数据移出结果的长度,因为偏移量超过了整数的长度,因此它肯定会导致信息丢失,因此会出现警告。
我正在玩一些 endian/big 字节序转换,发现一些有点令人困惑但也很有趣的东西。
在第一个例子中,uint32_t
类型使用位移位转换字节顺序没有问题。它基本上将 uint32_t
整数转换为 uint8_t
的数组,并尝试访问每个字节和位移位。
示例 #1:
uint32_t htonl(uint32_t x)
{
uint8_t *s = (uint8_t*)&x;
return (uint32_t)(s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3]);
}
但是,如果我尝试在下面的 uint64_t
上做类似的事情,编译器会抛出一条关于“s[0] 宽度小于 56 位”的警告,如下面的示例 #2 所示。
示例 #2:
uint64_t htonl(uint64_t x)
{
uint8_t *s = (uint8_t*)&x;
return (uint64_t)(s[0] << 56 ......);
}
为了让它工作,我必须将每个字节提取到一个 uint64_t
中,这样我就可以像下面的示例 #3 一样进行位移而不会出现任何错误。
示例 #3:
uint64_t htonll2(uint64_t x)
{
uint64_t byte1 = x & 0xff00000000000000;
uint64_t byte2 = x & 0x00ff000000000000;
uint64_t byte3 = x & 0x0000ff0000000000;
uint64_t byte4 = x & 0x000000ff00000000;
uint64_t byte5 = x & 0x00000000ff000000;
uint64_t byte6 = x & 0x0000000000ff0000;
uint64_t byte7 = x & 0x000000000000ff00;
uint64_t byte8 = x & 0x00000000000000ff;
return (uint64_t)(byte1 >> 56 | byte2 >> 40 | byte3 >> 24 | byte4 >> 8 |
byte5 << 8 | byte6 << 24 | byte7 << 40 | byte8 << 56);
}
我对 Example #1
和 Example #2
有点困惑,据我所知,s[i]
都是 uint8_t
大小,但如果只是移动 32 位或更少位完全没有问题,但移动 56 位时就会出现问题。我在 Ubuntu 上使用 GCC 8.3.0 运行 这个程序。
在这种情况下,编译器是否将 s[i]
隐式转换为 32 位数字? sizeof(s[0])
当我向其中添加调试消息时为 1。
类型小于 int
的值在表达式中使用时 提升 为 int
。假设 int
在您的平台上是 32 位的,这在转换 32 位值时适用于 大多数 情况。如果您将 1 位移入符号位,它将不起作用。
在 64 位的情况下,这意味着您试图移动一个超过其位长度的值,这是未定义的行为。
在这两种情况下,您都需要将每个字节都转换为 uint64_t
,以允许轮班正常工作。
s[0]
表达式有一个 8 位宽的整数类型,当被移位运算符操作时,它被提升为一个 32 位无符号整数——所以第一个例子中的 s[0] << 24
有效好的,因为移动 24 不会超过 uint
长度。
OTOH 移动 56 位会将数据移出结果的长度,因为偏移量超过了整数的长度,因此它肯定会导致信息丢失,因此会出现警告。