来自ISO/IEC 9899:TC3,6.3.1.3 有符号和无符号整数(在 6.3 转换下):
- 当整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新类型表示,则保持不变。
- 否则,如果新类型是无符号的,则通过在新类型中可以表示的最大值的基础上反复加减一,直到该值在新类型的范围内。
虽然这听起来有点令人费解,但它回答了以下问题。
如果将其强制转换为 u8,为什么需要 & 0xff?
不需要,因为演员会自动进行遮罩。
谈到主题中的问题,OP的最后一个建议是:
#define HIBYTE(w) ((uint8_t)(((uint16_t)(w) >> 8)))
#define LOBYTE(w) ((uint8_t)(w))
对于所有无符号值,这将按预期工作。有符号值将始终由宏转换为无符号值,在二进制补码的情况下不会改变表示,因此计算结果定义明确。然而,假设二进制补码是不可移植的,因此该解决方案对于有符号整数不是严格可移植的。
为有符号整数实现一种可移植的解决方案将非常困难,甚至可能会质疑这种实现的含义:
- 结果应该是签名还是未签名?
- 如果结果应该是无符号的,它实际上并不符合初始数字的高/低字节,因为可能需要更改表示来获得它。
- 如果结果应该签名,则必须进一步指定。
>>
例如,负值的结果是实现定义的,因此获得可移植的明确定义的“高字节”听起来很有挑战性。人们应该真正质疑这种计算的目的。
由于我们正在扮演语言律师,我们可能想知道(uint16_t)(w) >> 8
. 无符号似乎是显而易见的答案,但事实并非如此,因为整数提升规则。
除其他外,整数提升适用于如下指定的对象或表达式。
整数类型的对象或表达式,其整数转换等级小于或等于 int 和 unsigned int 的等级。
这种情况下的整数提升规则指定为:
如果一个 int 可以表示原始类型的所有值,则将该值转换为 int;
典型的 32 位或 64 位机器上的左操作数就是这种情况。
幸运的是,在这种情况下,转换后的左操作数仍然是非负数,这使得结果>>
定义明确:
E1 >> E2 的结果是 E1 右移 E2 位位置。如果 E1 具有无符号类型或 E1 具有有符号类型和非负值,则结果的值是 E1 / 2 E2的商的整数部分。