为什么当左操作数具有负值时,左移操作会引发未定义行为?

22 浏览
0 Comments

为什么当左操作数具有负值时,左移操作会引发未定义行为?

在C中,当左操作数为负值时,按位左移操作会引发未定义行为。

ISO C99(6.5.7/4)中的相关引用:

E1 << E2 的结果是将 E1 左移 E2 位,空出的位用零填充。如果 E1 具有无符号类型,则结果的值为 E1 × 2E2,对结果类型可表示的最大值加 1 进行取模。如果 E1 具有有符号类型且非负值,并且 E1 × 2E2 可以表示为结果类型,则结果为该值;否则,行为是未定义的

但在C++中,行为是明确定义的。

ISO C++-03(5.8/2)中的相关引用:

E1 << E2 的值是将 E1(解释为位模式)左移 E2 位,空出的位用零填充。如果 E1 具有无符号类型,则结果的值为 E1 乘以 2 的 E2 次幂,对于类型 unsigned long,取模为 ULONG_MAX+1,否则为 UINT_MAX+1。

[注意:常量 ULONG_MAX 和 UINT_MAX 定义在头文件中。]

这意味着

int a = -1, b=2, c;
c= a << b ;

在C中引发未定义行为,但在C++中行为是明确定义的。

是什么原因使得ISO C++委员会认为该行为是明确定义的,而与C中的行为相反?

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

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

P.S:请不要给出像“这是未定义行为,因为标准这样规定”的答案。:P

0
0 Comments

左移操作在左操作数为负值时会引发未定义行为的原因是,有符号整数类型的设计如此。如果数学操作的结果不能适应目标类型(即溢出或下溢),则对于带符号类型的任何操作都会有未定义行为。

对于左移操作,如果值为正数或0,则将操作符定义为乘以2的幂是有意义的,因此一切都正常,除非结果溢出,这并没有什么奇怪的地方。如果值为负数,则可以将其解释为乘以2的幂,但是如果仅从位移的角度考虑,这可能会让人感到困惑。显然,标准委员会希望避免这种歧义。

我的结论是:

  • 如果要进行实际的位模式操作,请使用无符号类型
  • 如果要将一个值(有符号或无符号)乘以2的幂,请直接执行此操作,例如 i * (1u << k)

无论如何,您的编译器都将将此转换为合理的汇编代码。

设置二进制补码数的符号位等效于将无限数量的位设置为左侧。32位数中可表示的值是指在第31位左侧的所有位具有相同值的值。将负的二进制补码值进行位移并没有什么异常或异常之处,除非存在超过符号位的值,其状态与符号位不匹配。

0
0 Comments

C中的位左移操作,当左操作数为负值时,会引发未定义行为。但是在C++中,行为是定义明确的。那么为什么C和C++会有这种差异呢?

简单的答案是:因为标准是这样规定的。

更详细的答案是:这可能与C和C++都允许负数的其他表示方式(除了2的补码)有关。对于可能在其他硬件上使用的语言,包括一些陌生的或者旧的机器,给出较少的保证可以实现这一点。

由于某种原因,C++标准化委员会决定增加一点关于位表示如何变化的保证。但是,由于负数仍然可以通过1的补码或者符号+大小表示法进行表示,所以结果的可能性仍然存在差异。

假设使用16位的int类型,我们有以下结果:

-1 = 1111111111111111 // 2的补码

-1 = 1111111111111110 // 1的补码

-1 = 1000000000000001 // 符号+大小表示法

左移3位后,我们得到:

-8 = 1111111111111000 // 2的补码

-15 = 1111111111110000 // 1的补码

8 = 0000000000001000 // 符号+大小表示法

那么是什么促使ISO C++委员会认为这种行为是定义明确的,而不同于C中的行为呢?

我猜他们提供这个保证是为了在你知道自己在做什么时(比如你确定你的机器使用2的补码),你可以适当地使用左移运算符。

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

我需要查看标准来确认。但是你可能是对的。在2的补码机器上,没有带符号扩展的右移操作并不特别有用。所以,当前的状态肯定比要求空出的位被填充为零要好,因为这给了进行符号扩展的机器留下了空间,尽管这并不是保证的。

当编写标准时,目标之一是尽可能地确保,如果任何实现在某种情况下执行了某些有用的操作,符合标准的实现也应该被允许执行类似的操作。当一个实现在标准范围之外以某种方式有用地陷入时,被标记为引发未定义行为。C标准的作者可以想象到,某些实现在左移一些负值时可能会陷入,而且某些人可能会发现这很有用,所以行为被保留为未定义。

一些现有的实现在右移时进行零填充,而其他实现进行符号扩展,因为一些为前者编写的代码可能依赖于这种行为,所以被标记为实现定义的。我认为当C++委员会意识到可能有一些平台在左移负值时可能会陷入,但实际上没有这样的平台,而且允许未来的实现开始这样做没有任何好处时,他们修复了左移操作的行为。

0
0 Comments

左移操作在左操作数具有负值时会引发未定义行为的原因是因为C++标准规定:如果左操作数具有有符号类型且非负值,并且E1×2^E2在结果类型中是可表示的,则结果值为E1×2^E2;否则,行为是未定义的。

具体来说,这是因为左移操作需要确定被移出的位和剩下的位应该如何处理。对于无符号类型,剩下的位将被填充为零。但是对于有符号类型,尤其是负值,左移操作的结果是没有定义的。

解决方法是避免在左移操作中使用负值的左操作数。可以通过将左操作数转换为无符号类型来解决这个问题,或者通过使用其他操作符(如右移操作符)来达到相同的目的。此外,还可以使用条件判断来确保左移操作不会导致未定义行为。

总之,左移操作在左操作数具有负值时会导致未定义行为,这是因为C++标准没有明确规定对于这种情况应该如何处理。为了避免这种问题,应该尽量避免在左移操作中使用负值的左操作数,并采取适当的处理措施,如类型转换或条件判断。

0