左右移位负整数是否定义了行为?

use*_*214 17 c++ bit-shift undefined-behavior

我知道,正确移位负签名类型取决于实现,但是如果我执行左移怎么办?例如:

int i = -1;
i << 1;
Run Code Online (Sandbox Code Playgroud)

这个定义明确吗?

我认为该标准没有说明带有签名类型的负值

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

它只阐明如果结果在签名类型中无法表示,则行为未定义.

R. *_*des 28

你没有正确地阅读那句话.标准定义如果:左操作数具有有符号类型非负值,并且结果是可表示的(以前在同一段落中为无符号类型定义).在所有其他情况下(注意在该句中使用分号),即,如果未验证这些条件中的任何一个,则行为是不确定的.


sup*_*cat 17

当C标准被编纂时,不同的平台在左移负整数时会做不同的事情.在其中一些中,行为可能会触发特定于实现的陷阱,其行为可能在程序的控制之外,并且可能包括随机代码执行.尽管如此,为这些平台编写的程序可能会利用这种行为(程序可能会指定用户在运行之前必须执行某些操作来配置系统的陷阱处理程序,但程序可能会利用此行为适当配置的陷阱处理程序).

C标准的作者不想说必须修改负数左移的机器的编译器以防止这种陷阱(因为程序可能依赖它),但是如果左移负数则允许触发一个可能导致任意行为(包括随机代码执行)的陷阱,这意味着左移一个负数允许做任何事情.因此未定义的行为.

实际上,直到大约5年前,99 +%的编译器编写的机器使用了二进制补码数学(意味着自1990年以来制造的机器数量的99%以上)将始终如一地产生以下行为,x<<y并且x>>y在某种程度上代码依赖因为这种行为被认为不比假定char为8位的代码更不便携.C标准没有强制要求这样做,但是任何想要与广泛的现有代码兼容的编译器作者都会遵循它.

  • if y是有符号类型,x << y并且x >> y被评估为好像y被转换为unsigned.
  • 如果x是类型int,x<<y则相当于(int)((unsigned)x << y).
  • 如果x是类型int和正面,x>>y相当于(unsigned)x >> y.如果x是类型int和负数,x>>y则相当于`〜(〜((unsigned)x)>> y).
  • 如果x是类型long,则适用类似的规则,但unsigned long不是unsigned.
  • 如果x是N位型和y大于N-1时,则x >> yx << y可任意地产生零,或好像右边的操作数是可作用y % N; 它们可能需要额外的时间与y[注意在32位机器上,如果y是负数,这可能是很长一段时间,虽然我只知道一台机器实际上会运行超过256个额外的步骤].编译器在选择时不一定一致,但总是会返回其中一个指示值而没有其他副作用.

遗憾的是由于某些原因我无法理解,编译器编写者已经决定,编程器应该假设不能执行任何行为未强制执行的任何转移,而不是允许程序员指出编译器应该使用什么假设来删除死代码.按C标准.因此,给出如下代码:

uint32_t shiftleft(uint32_t v, uint8_t n)
{
  if (n >= 32)
    v=0;
  return v<<n;
}
Run Code Online (Sandbox Code Playgroud)

编译器可以确定因为当n是32或更大时代码将参与未定义的行为,编译器可以假设if将永远不会返回true,并且因此可以省略代码.因此,除非或者直到有人提出恢复经典行为并允许程序员指定哪些假设值得删除死代码的C标准,否则不建议将此类结构用于任何可能提供给超现代编译器的代码.

  • 感谢这个精彩的答案 - 即使它不是被接受的答案,它也非常有用. (2认同)