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 #1Example #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 位会将数据移出结果的长度,因为偏移量超过了整数的长度,因此它肯定会导致信息丢失,因此会出现警告。