在C语言中对于uint64_t变量的位移操作

4

我有以下示例代码:

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

结果将是:
x=0xfffffffff0ffffff
y=0xfffff

有人能解释一下这个区别吗?为什么x要计算64位而y只计算32位?


整数字面量默认不是int类型吗? C++中十六进制或八进制表示的整数字面量的默认类型是什么? - phuclv
4个回答

6

默认操作是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;

挑剔一点说,默认值是(有符号的)int,无论它是什么。 - Lundin
严格来说,在32位系统中,0xFF<<24是未定义的行为,结果可能是任何值。 - Lundin
我不能同意。行为应该是可预测的。如果'a'的类型是'char',那么0xff<<24将会得到一个负值;否则,如果'a'的类型是short或int,它将是一个正值。 - ciphor

2
其他帖子已经解释了它为什么这样做。但是为了获得预期的结果:
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

2
由于0x0f << 24int类型下是一个正数,所以它会被符号扩展为一个正数,即0x00000000_0f000000(下划线只是为了可读性,C语言不支持这种语法)。然后这个数会被反转成你看到的结果。
另一方面,0xff << 24是负数,因此它的符号扩展方式也不同。

严格来说,在32位系统中,0xFF<<24是未定义的行为,结果可能是任何值。 - Lundin

1

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

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

ISO 9899:2011 6.5.7/4:

"E1 << E2 的结果是将 E1 左移 E2 位,空出的位用零填充。" /--/
"如果 E1 具有带符号类型和非负值,并且 E1 × 2E2 可以在结果类型中表示,则该值为结果;否则,行为未定义。"
因此,表达式 0xFF<<24 不能使用。程序可以在此之后打印任何垃圾值。
但是,如果我们忽略它并关注 0x0F<<24:
- 0x0F000000 仍然是一个(有符号)int。 ~运算符应用于此。 - 结果是 0xF0FFFFFF,仍然是有符号 int。在几乎任何系统上,这个32位十六进制数等于二进制补码中的负数。 - 在赋值期间,将此有符号 int 转换为 uint64_t 类型。这分两步完成,首先将其转换为有符号64位,然后将该有符号64位转换为无符号64位。"
这样的错误就是为什么编码规范MISRA-C包含了许多规则来禁止在表达式中使用整数字面值。符合MISRA-C标准的代码必须在每个整数字面值后使用 u 后缀(MISRA-C:2004 10.6),并且不允许对带符号整数进行位运算(MISRA-C:2004 12.7)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接