将 unsigned char 移动超过 8 位

shifting an unsigned char by more than 8 bits

这段代码让我有点困扰:

typedef struct _slink{
    struct _slink* next;
    char type;
    void* data;
}

假设这描述的是文件中的 link,其中数据为 4 字节长,表示地址或整数(取决于 link 的类型)

现在我正在考虑将文件中的数字从小端重新格式化为大端,所以我想做的是在写回文件之前更改字节的顺序,即 对于 0x01020304,我想将它转换为 0x04030201,所以当我写回它时,它的小端表示将看起来像 0x01020304 的大端表示,我通过乘以 i'th byte by 2^8*(3-i),其中 i 介于 0 和 3 之间。现在这是实现它的一种方式,这里让我感到困扰的是,这是将字节移动超过 8 位。 . (L 是类型 _slink*)

int data = ((unsigned char*)&L->data)[0]<<24) + ((unsigned char*)&L->data)[1]<<16) + 
                    ((unsigned char*)&L->data)[2]<<8) + ((unsigned char*)&L->data)[3]<<0)

任何人都可以解释为什么这真的有效吗?没有明确地将这些字节转换为整数开始(因为它们只有 1 个字节,但最多移动 24 位) 提前致谢。

任何小于 int 的整数类型在表达式中使用时 提升 为类型 int

所以移位实际上应用于类型 int 而不是类型 char 的表达式。

Can anyone please explain why this actually works?

转变不是作为 unsigned char 发生,而是作为提升为 int1 的类型发生。 .

代码仍然存在问题的原因。

32 位 int

int 1 移到符号位置是 未定义的行为 UB。另见 .

((unsigned char*)&L->data)[0]<<24)  // UB

16 位 int

即使类型为 unsigned,移位或更多位宽度也不够精确。 int 就是上面的UB。也许那时 OP 只想要一个 2 字节的字节序交换?

备选

const uint8_t *p = &L->data;
uint32_t data = (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | //
    (uint32_t)p[2] << 8 | (uint32_t)p[3] << 0;

为迂腐

如果 int 使用非 2 的补码,从 ((unsigned char*)&L->data)[0]<<24) 添加负值会弄乱数据模式。 Endian 操作最好使用 unsigned 类型来完成。


from little-endian to big-endian

此代码不会在这 2 个字节序之间交换。它是本机字节序交换的大字节序。当此代码在 32 位 unsigned 小端计算机上为 运行 时,它实际上是 big/little 交换。在 32 位 unsigned 大端机器上,它可能是一个空操作。


1 ... 或者 select 平台上的 unsigned UCHAR_MAX > INT_MAX.