当左侧操作数具有负值时,为什么左移操作会调用未定义的行为?

Pra*_*rav 42 c c++ bit-shift undefined-behavior language-lawyer

在C中,当左侧操作数具有负值时,左移位操作会调用未定义的行为.

ISO C99相关引用(6.5.7/4)

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

但在C++中,行为定义明确.

ISO C++ - 03(5.8/2)

E1 << E2的值是E1(解释为位模式)左移E2位位置; 空位是零填充的.如果E1具有无符号类型,则结果的值为E1乘以上升到功率E2的数量2,如果E1的类型为无符号长,则减少模ULONG_MAX + 1,否则为UINT_MAX + 1.[注意:标题中定义了常量ULONG_MAX和UINT_MAX).]

这意味着

int a = -1, b=2, c;
c= a << b ;
Run Code Online (Sandbox Code Playgroud)

在C中调用未定义的行为,但行为在C++中定义良好.

是什么迫使ISO C++委员会考虑与C中的行为相对应的行为?

另一方面,implementation defined当左操作数为负时,行为是按位右移操作,对吗?

我的问题是为什么左移操作在C中调用未定义的行为,为什么右移操作符只调用实现定义的行为?

PS:请不要给出答案,例如"这是未定义的行为,因为标准是这样说的".:P

ybu*_*ill 32

您复制的段落是关于无符号类型的.的行为在C++中未定义的.从上一个C++ 0x草案:

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

编辑:看看C++ 98论文.它根本没有提到签名类型.所以它仍然是未定义的行为.

右移负面是实施定义,对.为什么?在我看来:实现定义很容易,因为左边的问题没有截断.当你向左移动时,你不仅要说出从右边偏移了什么,还要说出其余部分会发生什么,例如用二进制补码表示,这是另一个故事.

  • @Prasoon Saurav:该段落是当前C++ 0x最终草案的一部分,它表明C++标准委员会认为这是当前标准的一个缺陷并通过实际声明它未定义来修复它 - 而不是隐含地没有定义结果. (9认同)
  • @David"编辑:看看C++ 98论文.它根本没有提到签名类型.所以它仍然是未定义的行为." 我不同意这种解释."E1 << E2的值是E1左移E2位位置;空位是零填充." 是一个明确的声明,不排除签名类型.我认为他们只是忽略了签署负面操作数的情况. (3认同)
  • @JohannesSchaub-litb:由于 5p5,它明确未定义:“如果在评估表达式期间,结果未在数学上定义 ** 或不在其类型的可表示值范围内**,则行为未定义”。您是正确的,第一部分适用于所有类型,第二部分强制对无符号类型进行操作以生成可表示的值,因为否则如果任何位溢出,无符号左移也会导致未定义的行为。 (2认同)

sel*_*tze 17

在C中,当左侧操作数具有负值时,左移位操作会调用未定义的行为.[...]但是在C++中,行为定义得很好.[...]为什么[...]

答案很简单:因为标准是这样说的.

更长的答案是:它可能与C和C++除了2的补码之外还允许负数的其他表示这一事实有关.对将要发生的事情提供较少的保证使得可以在其他硬件上使用这些语言,包括模糊和/或旧机器.

出于某种原因,C++标准化委员会感觉像是对位表示如何变化提供了一些保证.但由于负数仍然可以通过1的补码或符号+幅度表示,因此结果值的可能性仍然存在变化.

假设16位整数,我们将拥有

 -1 = 1111111111111111  // 2's complement
 -1 = 1111111111111110  // 1's complement
 -1 = 1000000000000001  // sign+magnitude
Run Code Online (Sandbox Code Playgroud)

向左移动3,我们会得到

 -8 = 1111111111111000  // 2's complement
-15 = 1111111111110000  // 1's complement
  8 = 0000000000001000  // sign+magnitude
Run Code Online (Sandbox Code Playgroud)

是什么迫使ISO C++委员会考虑与C中的行为相对应的行为?

我猜他们做了这个保证,这样当你知道你在做什么时(即当你确定你的机器使用2的补码时)你可以适当地使用<<.

另一方面,当左操作数为负时,行为是为按位右移操作定义的实现,对吗?

我必须检查标准.但你可能是对的.在2的补码机上没有符号扩展的右移不是特别有用.因此,当前状态肯定比要求腾空的零填充要好,因为它为进行符号扩展的机器留出了空间 - 即使它不能保证.


Jen*_*edt 7

要回答标题中所述的真实问题:对于有符号类型的任何操作,如果数学运算的结果不适合目标类型(欠量或溢出),则会出现未定义的行为.有符号整数类型就是这样设计的.

对于左移操作,如果值为正或0,则操作符的定义为2的幂乘法是有意义的,所以一切正常,除非结果溢出,没有什么令人惊讶的.

如果该值为负,则可以使用2的幂对乘法进行相同的解释,但如果您只考虑位移,则这可能会令人惊讶.显然,标准委员会希望避免这种模糊性.

我的结论:

  • 如果要进行实际位模式操作,请使用无符号类型
  • 如果你想将一个值(有符号或无符号)乘以2的幂,就可以这样做

    我*(1u << k)

在任何情况下,您的编译器都会将其转换为合适的汇编程序