uint64_t变量中C的按位移位运算

我有以下示例代码:

uint64_t x, y; x = ~(0xF<<24); y = ~(0xFF<<24); 

结果将是:

 x=0xfffffffff0ffffff y=0xfffff 

有人可以解释这个区别吗? 为什么x计算超过64位而y仅计算在32位?

默认操作是32位。

 x=~(0xf<<24); 

此代码可以反汇编为以下步骤:

 int32_t a; a=0x0000000f; a<<=24; // a=0x0f000000; a=~a; // a=0xf0ffffff; x=(uint64_t)a; // x = 0xfffffffff0ffffff; 

和,

 y = ~(0xFF<<24); int32_t a; a=0x000000ff; a<<=24; // a=0xff000000; a=~a; // a=0x00ffffff; x=(uint64_t)a; // x = 0x000000000ffffff; 

因为0x0f << 24在被视为int时是正数,所以它被符号扩展为正数,即到0x00000000_0f000000 (下划线只是为了可读性,C不支持这种语法)。 然后将其转换为您所看到的内容。

另一方面, 0xff << 24是负数,因此它的符号扩展方式不同。

其他海报已经说明了为什么会这样做。 但要获得预期的结果:

 uint64_t x, y; x = ~(0xFULL<<24); y = ~(0xFFULL<<24); 

或者你可以这样做(我不知道这是否比上面的慢):

 uint64_t x, y; x = ~(uint64_t(0xF)<<24); y = ~(uint64_t(0xFF)<<24); 

然后:

 x = 0xfffffffff0ffffff y = 0xffffffff00ffffff 

您的程序中有未定义的行为,因此可能发生任何事情。

  • 整数文字0xF或0xFF的类型为int ,相当于signed int 。 在这个特定的平台上, int显然是32位。
  • 整数文字24也是(带符号) int
  • 当编译器评估<<操作时,两个操作数都是(已签名) int因此不会发生隐式类型提升。 因此<<操作的结果也是(签名的) int
  • 值0xF << 24 = 0x0F000000作为非负值适合(带符号) int ,因此一切正常。
  • 值0xFF << 24 = 0xFF000000 不适合 (signed) int ! 在这里,调用未定义的行为,并且可能发生任何事情。

ISO 9899:2011 6.5.7 / 4:

“E1 << E2的结果是E1左移E2位位置;空位用零填充。” / - /

“如果E1具有带符号类型和非负值,并且E1×2E2在结果类型中可表示,那么这就是结果值;否则,行为是未定义的。

因此不能使用表达式0xFF << 24。 该程序可以随后打印任何垃圾值。

但如果我们忽略那一个并专注于0x0F <24:

  • 0x0F000000仍然是(签名) int 。 〜运算符适用于此。
  • 结果是0xF0FFFFFF,它仍然是一个signed int。 几乎在任何系统上,这个32位hex等于二进制补码中的负数。
  • 在赋值期间,此signed int将转换为uint64_t类型。 这分两步完成,首先将其转换为带符号的64位,然后将带符号的64转换为无符号64。

像这样的错误是为什么编码标准MISRA-C包含许多规则来禁止在这样的表达式中使用整数文字。 符合MISRA-C的代码必须在每个整数文字后使用u后缀(MISRA-C:2004 10.6),并且不允许代码对有符号整数执行按位运算(MISRA-C:2004 12.7)。