是“-1 >> 5;”C中未指明的行为?

问题描述 投票:38回答:4

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;是未指定的行为。

那么,Qazxswpoi在C中是否有未指明的行为?哪个是对的?

c gcc bit-manipulation language-lawyer unspecified-behavior
4个回答
38
投票

两者都是正确的。实现定义的行为是一种特定类型的未指定行为。

引用-1>>5;的3.4.1节定义了“实现定义的行为”:

1实现定义的行为

未指定的行为,其中每个实现都记录了如何进行选择

2示例实现定义的行为的示例是当有符号整数向右移位时高阶位的传播。

从3.4.4节定义“未指明的行为”:

1个未指明的行为

使用未指明的值,或本国际标准提供两种或更多种可能性的其他行为,并且在任何情况下都不会对其进行任何进一步的要求

2示例未指定行为的示例是评估函数参数的顺序。

至于GCC,你总会得到相同的答案,因为操作是实现定义的。它通过符号扩展实现负数的右移

来自the C standard

对有符号整数进行一些按位运算的结果(C90 6.3,C99和C11 6.5)。

按位运算符作用于值的表示,包括符号和值位,其中符号位被立即考虑在最高值位之上。签署的GCC documentation通过符号扩展对负数进行处理。

作为C语言的扩展,GCC不使用C99和C11中给出的宽容度来将签名的>>的某些方面视为未定义。然而,<<(和-fsanitize=shift)将诊断此类病例。它们也被诊断为需要常量表达式。


14
投票

“未指明的行为”和“实施定义”并不矛盾。它只是意味着C标准没有规定需要发生什么,并且各种实现可以做他们认为“正确”的事情。

在一个编译器上运行多次并获得相同的结果只意味着该特定编译器是一致的。您可能会在不同的编译器上获得不同的结果。


2
投票

实现定义的行为是未指定行为的子类,即标准未指定的行为。

C89的缺陷报告#154向委员会询问-fsanitize=undefined的限制是什么;委员会回答说实现可以定义它想要的任何行为,并且不需要保持不变。

实现需要做的是记录如何做出这种选择,而不是其他一类未指定的行为,其中一致的实现甚至不需要知道如何做出选择,可能是因为对于这些实现的大部分实现会说“随机”或“取决于编译器优化级别”或“取决于局部变量的寄存器分配”。


2
投票

我没有得到任何目前的答案。 C标准明确指出,对负数进行右移是实现定义的行为。这不是未指明的行为,这意味着别的东西。正如你所指出的那样(C176.5.7§5):

E1 >> E2的结果是E1右移E2位位置。 / - / 如果E1具有带符号类型和负值,则结果值是实现定义的。

这意味着编译器必须记录它的行为方式。期。

实际上:文档必须告诉编译器是使用算术右移还是逻辑右移。


这与未指定的行为相反,后者是不需要记录的特定于实现的行为。在两种情况下使用未指定的行为:

  • 当编译器行为可能是一个实现秘密时,不应强迫编译器供应商向其竞争对手透露。
  • 当编译器无法记录如何操作OS和RAM存储器单元等底层细节时。

例如,编译器不需要在代码中记录评估顺序,如下所示:

implementation-defined behaviour

记录子表达式的计算顺序将揭示编译器的内部表达式树和优化器如何工作的细节,这反过来将揭示编译器生成更好的代码或编译速度比竞争对手更快的原因。当C标准最初编写时,这是一件大事。如今,当有一些很棒的开源编译器时,它就不那么秘密了。

同样,编译器不需要记录此代码打印的内容:

a  = f1() + f2();
a += f1() + f2();

int a; int ptr = &a; printf("%d", *ptr); 是一个不确定的值,输出未指定 - 实际上输出取决于之前存储在该特定RAM单元中的内容。我们称之为“垃圾价值”。 (在大喊“UB”之前,请参阅a)。

© www.soinside.com 2019 - 2024. All rights reserved.