当sizeof(int)== 4时,1 << 31在C中定义得很好

Ily*_*hin 25 c bit-shift undefined-behavior language-lawyer

根据这个问题的答案:

E1 << E2的结果是E1左移E2位位置; 腾出的位用零填充.如果E1具有无符号类型,则结果的值为E1×2 E2,比结果类型中可表示的最大值减少一个模数.如果E1具有带符号类型和非负值,并且E1×2 E2可在结果类型中表示,那么这就是结果值; 否则,行为未定.

这似乎意味着1 << 31未定义.

但是,如果我使用,GCC不会发出警告1 << 31.它确实发出一个问题1 << 32. 链接

那是哪个呢?我误解了标准吗?海湾合作委员会有自己的解释吗?

chq*_*lie 22

否:1 << 31如果类型int只有31个值位,则具有未定义的行为.

1U << 31可以并且评估0x80000000类型unsigned int是否有32个值位.

在字节有8位的系统中,sizeof(int) == 4平均值int最多有31个值位,因此将1乘以31位是未定义的.相反,在一个系统上CHAR_BIT > 8,可以写1 << 31.

gcc如果提高警告级别,可能会发出警告.试试gcc -Wall -Wextra -W -Werror. clang会发出相同选项的警告.

为了解决麦克罗伊的言论,1 << 31没有评估为INT_MIN可靠.它可能会在您的系统上提供此值,但标准并不保证它,实际上标准将此描述为未定义的行为,因此您不仅可以不依赖它,还应避免它以避免虚假错误.优化器通常利用潜在的未定义行为来删除代码并破坏程序员的假设.

例如,以下代码可能编译为一个简单的return 1;:

int check_shift(int i) {
   if ((1 << i) > 0)
       return 1;
   else
       return 0;
}
Run Code Online (Sandbox Code Playgroud)

Godbolt的编译器探测器所支持的编译器都没有,但这样做不会破坏整合.

  • @MichaëlRoy - 根据定义,它是未定义的. (13认同)
  • @MichaëlRoy:我担心C标准的语言很清楚.这是措辞,因为不是所有的计算机都是英特尔PC.如果在不同的CPU上,移位有符号值可能不会溢出到符号位,或者触发异常,CPU可能不会使用二进制补码表示..."1 << 31"的行为会给出不同的结果的CPU.这是标准将边界情况描述为**未定义行为**的典型原因.依赖C标准无根据的行为的后果可能很难找到错误. (7认同)
  • @MichaëlRoy:它可能会在你的系统上给出这个值,但标准并不能保证它,事实上标准把它描述为未定义的行为,因此你不仅可以不依赖它,你应该避免它来防止虚假的错误.优化器通常利用潜在的未定义行为来删除代码并破坏程序员的假设. (6认同)
  • @MichaëlRoy:再说一次,我们不是在谈论asm/hardware.我们在谈论C语言.C编译器/优化器在后一个域中工作.如果你想要一个明确定义的方法来获得C**中的'0x80000000`**,那么你应该做'1U << 31`. (6认同)
  • @MichaëlRoy*你混淆了位移和乘法.*不,他不是.他实际上***引用***[**6.5.7按位移位算子**,第4段](http://port70.net/~nsz/c/c11/n1570.html#6.5.7p4)的[C标准](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf).注意6.5.7节的标题. (6认同)
  • @IlyaLesokhin:"未定义的行为"并不意味着"所有编译器都必须生成警告". (5认同)
  • @MichaëlRoy"*如果E1有一个带符号的类型和非负值,并且E1 x 2 ^ E2在结果类型中是可表示的,那么这就是结果值;否则,行为是未定义的.*"`E1 x 2 ^ E2 =在这种情况下,1 x 2 ^ 31`.这是'2147483648`而不是,它不能用31位(或32包括符号)表示.`(int)0x80000000`不是`2147483648` (4认同)
  • @MichaëlRoy - 我不确定这是怎么相关的? (2认同)

小智 17

GCC没有对此发出警告的原因是因为1 << 31 在C90中有效的(但是实现定义的),并且即使在现代C++中也是有效的(但是实现定义的).C90定义<<为一个位移,然后说对于无符号类型,其结果是乘法的结果,但对于有符号类型没有这样的东西,它隐含地使它有效并使其被一般的措辞覆盖,即按位运算符已实现已签名类型的定义方面.C++现在定义<<为与相应的无符号类型相乘,结果转换回有符号类型,也是实现定义的类型.

C99和C11确实使其无效(说明行为未定义),但允许编译器接受它作为扩展.为了与现有代码兼容,并在C和C++前端之间共享代码,GCC继续这样做,但有一个例外:您可以使用-fsanitize=undefined获取检测到的未定义行为来在运行时中止您的程序,并且这个行为可以处理1 << 31,但仅在编译为C99或C11时.


Oli*_*rth 9

它会调用未定义的行为,正如其他答案/评论所解释的那样.但是,为什么GCC不发出诊断.

实际上有两件事可能导致左移的未定义行为(均来自[6.5.7]):

  1. 如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义.

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

显然GCC检测到第一个(因为这样做很简单),但后者不是.