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

Is "-1>>5;" unspecified behavior in C?

C11 §6.5.7 第 5 段:

The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2*^E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.

但是,viva64 参考文档说:

int B;
B = -1 >> 5; // unspecified behavior

我 运行 这个代码在 GCC 上,它总是给出一个输出 -1.

所以,标准说 "If E1 has a signed type and a negative value, the resulting value is implementation-defined",但该文件说 -1>>5; 未指定的行为

那么,-1>>5; 是 C 中的未指定行为吗?哪个是正确的?

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

引用 the C standard 的第 3.4.1 节定义 "implementation-defined behavior":

1 implementation-defined behavior

unspecified behavior where each implementation documents how the choice is made

2 EXAMPLE An example of implementation-defined behavior is the propagation of the high-order bit when a signed integer is shifted right.

从第 3.4.4 节定义 "unspecified behavior":

1 unspecified behavior

use of an unspecified value, or other behavior where this International Standard provides two or more possibilities and imposes no further requirements on which is chosen in any instance

2 EXAMPLE An example of unspecified behavior is the order in which the arguments to a function are evaluated.

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

来自GCC documentation

The results of some bitwise operations on signed integers (C90 6.3, C99 and C11 6.5).

Bitwise operators act on the representation of the value including both the sign and value bits, where the sign bit is considered immediately above the highest-value value bit. Signed >> acts on negative numbers by sign extension.

As an extension to the C language, GCC does not use the latitude given in C99 and C11 only to treat certain aspects of signed << as undefined. However, -fsanitize=shift (and -fsanitize=undefined) will diagnose such cases. They are also diagnosed where constant expressions are required.

"Unspecified behavior"和"implementation defined"并不矛盾。这只是意味着C标准没有指定需要发生什么,各种实现可以做他们认为的事情"correct."

运行 它在一个编译器上多次运行并且得到相同的结果仅意味着 那个特定的编译器 是一致的。您可能会在不同的编译器上得到不同的结果。

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

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

实现需要做的是记录如何做出这个选择,而不是其他 class 未指定行为,在这种情况下,符合规范的实现不需要甚至懒得告诉 如何 做出选择,可能是因为对于这些大多数实现,文本会说 "at random" 或 "depending on the compiler optimization level" 或 "depending on the register allocation for local variables".

我目前没有得到任何答案。 C 标准明确指出右移负数是 实现定义的行为 。它是不是 未指定的行为,这意味着别的东西。正如您正确引用的那样(C17 6.5.7 §5):

The result of E1 >> E2 is E1 right-shifted E2 bit positions. /--/
If E1 has a signed type and a negative value, the resulting value is implementation-defined.

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

实践中:文档必须说明编译器是使用算术右移还是逻辑右移。


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

  • 当编译器行为可能是编译器供应商不应被迫向其竞争对手透露的实现秘密时。
  • 当编译器懒得记录底层细节(例如 OS 和 RAM 内存单元的工作方式)时。

例如,编译器不需要像这样记录代码中的求值顺序:

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

记录子表达式的求值顺序将揭示有关编译器内部表达式树和优化器如何工作的详细信息,这反过来又将揭示为什么编译器比竞争对手产生更好的代码或编译速度更快。这在最初编写 C 标准时是一件大事。如今,当有一些很棒的开源编译器出现时,情况就不那么好了,所以它不再是秘密。

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

int a;
int ptr = &a;
printf("%d", *ptr);

a 是一个不确定的值,输出未指定 - 实际上,输出取决于之前存储在特定 RAM 单元中的内容。我们称之为 "garbage value"。 (在喊 "UB" 之前,请参阅 (Why) is using an uninitialized variable undefined behavior?)。