C11§6.5.7第5段:
E1 >> E2
的结果是E1
右移E2
位位置。如果E1
有一个无符号类型,或者E1
有一个签名类型和一个 非负值,结果的值是不可分割的一部分E1 / 2*^E2
的商。 如果E1
有签名类型和否定 值,结果值是实现定义的。
但是,viva64参考文档说:
int B; B = -1 >> 5; // unspecified behavior
我在GCC上运行了此代码,并始终提供输出-1
。
所以,标准说是“如果E1有签名类型和负值,结果值是实现定义的”,但该文档说-1>>5;
未指明的行为。
那么,C中有-1>>5;
未指定的行为吗?哪个是对的?
答案 0 :(得分:38)
两者都是正确的。实现定义的行为是一种特定类型的未指定行为。
引用the C standard的第3.4.1节,其中定义了"实现定义的行为":
1 实施定义的行为
未指明的行为,其中每个实现记录了如何做出选择
2 示例实现定义的行为的示例是高阶位的传播 当有符号整数向右移位时。
从3.4.4节定义"未指明的行为":
1 未指明的行为
使用未指定的值或其他行为 国际标准提供两种或更多种可能性和强加 在任何情况下都没有选择进一步的要求
2 示例未指定行为的示例是评估函数参数的顺序。
对于GCC,您总是得到相同的答案,因为该操作是实现定义的。它通过符号扩展实现负数的右移
对有符号整数进行一些按位运算的结果(C90 6.3,C99和C11 6.5)。
按位运算符作用于值的表示,包括 符号位的符号和值位 紧接在最高值位之上。 签名
>>
会对此采取行动 符号扩展名为负数。作为C语言的扩展,GCC不使用给定的宽容度 在C99和C11中仅将签名
<<
的某些方面视为 未定义。但是,-fsanitize=shift
(和-fsanitize=undefined
)会 诊断此类病例。它们也被诊断为不变的 表达式是必需的。
答案 1 :(得分:14)
&#34;未指明的行为&#34;和&#34;实施定义&#34;并不矛盾。它只是意味着C标准没有规定需要发生什么,并且各种实现可以做他们认为正确的事情。&#34;
在一个编译器上运行多次并获得相同的结果只意味着特定编译器是一致的。您可能会在不同的编译器上获得不同的结果。
答案 2 :(得分:2)
实施定义的行为是未指定行为的子类,即标准未指定的行为。
C89的缺陷报告#154询问委员会implementation-defined behaviour的局限性是什么;委员会回答说,一个实现可以定义它想要的任何行为,并且不需要是恒定的。
实现需要做的是记录如何做出此选择,这与另一类未指明的行为相反,在后者中,一致的实现甚至无需费心地告诉如何之所以做出选择,可能是因为对于这些大多数实现,文本将说“随机”或“取决于编译器优化级别”或“取决于局部变量的寄存器分配”。
答案 3 :(得分:2)
我没有任何当前答案。 C标准明确指出,右移负数是实现定义的行为。这不是不是未指定的行为,这意味着其他原因。如您正确引用(C17 6.5.7§5):
E1 >> E2的结果是E1右移E2位的位置。 /-/
如果E1具有带符号的类型和负值,则结果值是实现定义的。
这意味着编译器必须记录其行为。期间。
实际上:文档必须告诉编译器是使用算术右移还是逻辑右移。
这与未指定的行为相反,后者是不需要需要记录的特定于实现的行为。在两种情况下会使用未指定的行为:
例如,编译器不需要用以下代码记录评估顺序:
a = f1() + f2();
a += f1() + f2();
记录子表达式的计算顺序将揭示有关编译器的内部表达式树和优化器如何工作的详细信息,而这反过来将揭示为什么编译器产生比竞争更好的代码或编译速度更快的原因。最初编写C标准时,这是一件大事。如今,当有一些出色的开源编译器时,这种情况已经不再那么严重了,所以这不再是秘密。
类似地,编译器不需要记录此代码显示的内容:
int a;
int ptr = &a;
printf("%d", *ptr);
a
是一个不确定的值,并且输出未指定-实际上,输出取决于之前在特定RAM单元中存储的内容。我们称之为“垃圾价值”。 (在大喊“ UB”之前,请参见(Why) is using an uninitialized variable undefined behavior?)。