size_t 0x1 << 31远大于size_t 0x1 << 30

mra*_*sen 4 c size-t

size_t注意到的一些行为使我感到困惑:

size_t zero = 0x1 << 32;
size_t big = 0x1 << 31;
size_t not_as_big = 0x1 << 30;
printf("0x1<<32: %zx\n0x1<<31: %zx\n0x1<<30: %zx\n", zero, big, not_as_big);
Run Code Online (Sandbox Code Playgroud)

结果是:

0x1<<32: 0
0x1<<31: ffffffff80000000
0x1<<30: 40000000
Run Code Online (Sandbox Code Playgroud)

现在,我明白size_t只保证至少是一个16位无符号整数,但我不明白为什么会0x1<<31结束它所做的值 - 尝试分配18 exabytes在我的程序上做了一个数字.

我在x86_64上使用LLVM.

Kar*_*l S 9

在C中移位有符号整数以使1进入符号位或甚至进一步未定义,因此编译器可以自由地执行以下操作:

0x1 << 32
Run Code Online (Sandbox Code Playgroud)

这里编译器看到一个32位的int(0x1),它被移位了32位.由于编译器可以以与更正确的移位一致的方式自由解释它,因此它将其解释为0x1_0000_0000并尝试将其转换为32位int,从而产生0x0000_0000,然后看到您稍后将结果分配给a size_t,通常是64位:0x0000_0000_0000_0000

0x1 << 31
Run Code Online (Sandbox Code Playgroud)

和以前一样,编译器可以随心所欲地做任何事情,因为1位会侵入符号位的位置.结果是0x8000_0000,这是一个负数 - INT_MIN准确.然后,它会看到您将该负数转换为64位,因此它将其扩展为1,与所有负数一样.结果是0xffff_ffff_8000_0000,最小的32位有符号整数存储为带符号的64位整数.

所有64位平台之间的正确和可移植方式是:

((size_t)1) << 32
((size_t)1) << 31
Run Code Online (Sandbox Code Playgroud)

  • 另一种可爱的方法是使用通常无用的结构:`sizeof(char)<< 31`. (3认同)

oua*_*uah 5

0x1是类型int和在32位的实现中int,两个表达式的评估:

0x1 << 32
Run Code Online (Sandbox Code Playgroud)

0x1 << 31
Run Code Online (Sandbox Code Playgroud)

调用未定义的行为.

要解决问题(但假设您不想保持zero对象评估0),请按照KarolS答案中的建议进行操作

(size_t) 1 << 32
Run Code Online (Sandbox Code Playgroud)

(size_t) 1 << 31
Run Code Online (Sandbox Code Playgroud)

假设size_t类型宽于32位,这是x64上的clang实现的情况.

  • 这是不这样做的原因,也是结果不与众不同的原因.这不是对如何产生这个特定值的解释,这也可能是有用的. (7认同)