在C ++ 11中,根据en.cppreference.com,
对于有符号和非负a,<< <<的值是 a * 2 b (如果可以用返回类型表示), 否则行为是不确定的。
我的理解是,由于255 * 2 24 不是
可表示为int32_t
的{{1}}的求值
产生未定义的行为。那是对的吗?可以吗
依赖于编译器?如果重要的话,这是IP16环境。
背景:来自an argument I am having,带有一个 arduino.stackexchange.com上的用户。据他说,“什么都没有 完全没有定义”:
您会注意到,很多位移是“实现定义的”。 因此,您不能在规格中引用章节。你得走了 到GCC文档,因为那是唯一可以说明的地方 你实际发生了什么。 gnu.org/software/gnu-c-manual/gnu-c-manual.html#Bit-Shifting- 负移位值只是“未定义”。
编辑:从目前的答案来看,我似乎对 C ++ 11标准是正确的。那么我的问题的关键是 该表达式调用 gcc 中的未定义行为。正如davmac所说的 在他的评论中,我问“ GCC是否是一种实现, 行为,即使语言标准未对其进行定义也是如此。”
从我链接到的gcc手册看来,它确实已经定义好了, 尽管我发现本手册的措词听起来更像是教程 而不是“语言法”。摘自PSkocik的回答(以及Kane对此的评论 答案),似乎它是未定义的。所以我还是有疑问。
我想我的梦想是在一些海湾合作委员会中发表明确的声明 说明以下内容的文档:1)gcc没有定义任何行为 在标准中明确未定义,或者2)gcc确实定义了 XX.XX版中的行为,并承诺保持所有定义 后续版本。
编辑2 :PSkocik删除了他的答案,我觉得很不幸,因为 它提供了有趣的信息。从他的回答中,凯恩的评论 答案和我自己的实验:
(int32_t) 255 << 24
使用clang编译时会产生运行时错误
和(int32_t)255<<24
-fsanitize=undefined
-fsanitize=undefined
编译时确实给出了运行时错误
(int32_t)256<<24
第2点与gcc在C ++ 11模式下的解释一致,
比标准更广泛地定义左移。根据第3点,
这个定义可能只是C ++ 14定义。但是,第3点是
不一致与the referenced manual是一个
该手册提供了gcc(C ++ 11模式)中g++ -std=c++11 -fsanitize=undefined
的完整定义
没有提示<<
可能不确定。
答案 0 :(得分:8)
随着时间的推移,这种情况发生了变化,这是有充分理由的,因此让我们回顾一下历史。请注意,在所有情况下,简单地定义static_cast<int>(255u << 24)
都是行为。也许只是这样做并回避所有问题。
C++11的原始措辞是:
E1 << E2
的值是E1
个左移E2
位的位置;空出的位为零。如果E1
具有无符号类型,则结果的值为E1×2E2
,与结果类型中可表示的最大值相比,模减少了1。否则,如果E1
具有带符号的类型且非负值,并且E1×2E2
在结果类型中可表示,则为结果值;否则,行为是不确定的。
255 << 24
在C ++ 11中是未定义的行为,因为结果值无法表示为32位带符号整数,它太大。
此未定义行为会引起一些问题,因为constexpr
必须诊断未定义行为-因此,一些常见的设置值方法会导致严重错误。因此,CWG 1457:
当前8.8 [expr.shift]第2段的措词使通过将(带符号的)1左移到符号位来创建给定类型的最负整数成为不确定的行为,即使这种情况并不罕见daccess-ods.un.org daccess-ods.un.org因此,该技术无法在大多数(二进制补码)体系结构上正常工作。因此,该技术不能在常量表达式中使用,否则将破坏大量代码。
这是针对C ++ 11的缺陷。从技术上讲,一个合格的C ++ 11编译器将实现所有缺陷报告,因此可以正确地说,在C ++ 11中,这是 not 未定义的行为。在C ++ 11中255 << 24
的行为被定义为-16777216
。
缺陷后的措辞可以在C++14中看到:
E1 << E2
的值是E1
个左移E2
位的位置;空出的位为零。如果E1
具有无符号类型,则结果的值为E1×2E2
,与结果类型中可表示的最大值相比,模减少了1。否则,如果E1
具有带符号的类型且非负值,并且E1×2E2
可表示为结果类型的相应的无符号类型,则该值,转换为结果类型是结果值;否则,行为是不确定的。
C ++ 17中的措词/行为没有变化。
但是对于C ++ 20,由于Signed Integers are Two's Complement(及其wording paper)的结果,措辞为greatly simplified:
E1 << E2
的值是E1×2E2
模2N
的唯一值,其中N
是结果类型的范围指数。
255 << 24
在C ++ 20中仍然定义了行为(具有相同的结果值),只是关于如何到达那里的规范变得更加简单,因为该语言不必围绕有符号整数的表示形式是实现定义的事实。
答案 1 :(得分:3)
“未定义行为是标准没有要求的行为。”这意味着它甚至可能是预期/正确的行为。 C ++标准规定了移位运算符。
8.5.7 Shift运算符[expr.shift] (C ++标准草案N4713)
- 操作数应为整数或无范围枚举类型,并执行整数提升。结果的类型是提升后的左操作数的类型。如果右操作数为负,或者大于或等于提升后的左操作数的位长度,则该行为未定义。否则,如果
E1
具有带符号的类型且非负值,并且E1×2E2
在结果类型的相应无符号类型中可表示,则该值(转换为结果类型)就是结果值;否则,行为是不确定的。
正如@rustyx在下面指出的那样,“措词“ E1×2E2
在结果类型的相应无符号类型中是可表示的”是C++14。不幸的是,C++11中的UB仍然是。”
答案 2 :(得分:3)
GNU C 编译器按照manual中的描述完全定义了左/右移位:
GCC仅支持两种补码整数类型,并且所有位模式均为普通值。
。 。
作为C语言的扩展,GCC并不使用C99和C11中给出的纬度仅将带符号的“
<<
”的某些方面视为未定义。但是,-fsanitize=shift
(和-fsanitize=undefined
)将诊断这种情况。它们还会被诊断为需要常量表达式的地方。
因此,这与您的发现相符-代码达到了您的期望,仅将诊断超出可用位(包括符号位)的溢出。
至于GNU C ++ 编译器,该文档似乎确实是lacking。我们只能猜测,尽管至少sanitizer似乎意识到了语言上的差异,但是尽管忽略了这一转变,但它在G ++中的工作方式与在GCC中的工作方式相同:
-fsanitize=shift
此选项使您可以检查移位操作的结果是否未定义。请注意,在C和C ++之间以及在ISO C90和C99等之间,确切地认为未定义的内容略有不同。
答案 3 :(得分:0)
对于C ++ 14之前的C ++编译器,如果您具有这样的功能:
// check if the input is still positive,
// after a suspicious shift
bool test(int input)
{
if (input > 0)
{
input = input << 24;
// how can this become negative in C++11,
// unless you are relying on UB?
return (input > 0);
}
else
{
return false;
}
}
然后将允许一个优化的编译器将其更改为此:
bool test(int input)
{
// unless you are relying on UB,
// input << 24 must fit into an int,
// and input is positive in that branch
return (input > 0);
}
每个人都很高兴,因为您在发行版本中获得了不错的加速。
但是,我不知道实际上对左移进行了此类优化的编译器,尽管将附加内容优化为seen in this example也是很普遍的。