"-1>>5;" C 中是未指定的行为吗?



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

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

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

引用C标准第3.4.1节,其中定义了"实现定义的行为":

1 实现定义的行为

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

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

从定义"未指定行为"的第 3.4.4 节开始:

1 未指定的行为

使用未指定的值或其他行为 国际标准提供了两种或两种以上的可能性,并强加了 在任何情况下都没有选择进一步的要求

2示例 未指定行为的一个示例是计算函数参数的顺序。

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

从 GCC 文档中:

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

按位运算符作用于值的表示形式,包括 符号位和值位,其中考虑符号位 紧挨着最高值位。签署>>行为 按符号扩展的负数。

作为C语言的扩展,GCC不使用给定的纬度 在 C99 和 C11 中,仅将已签名<<的某些方面视为 定义。但是,-fsanitize=shift(和-fsanitize=undefined)将 诊断此类病例。它们也被诊断为恒定 表达式是必需的。

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

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

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

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

实现需要做的是记录这种选择是如何做出的,而不是另一类未指定的行为,其中符合要求的实现甚至不需要费心告诉如何做出选择,可能是因为这些实现的大多数实现会说"随机"或"取决于编译器优化级别"或"取决于局部变量的寄存器分配"。

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

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

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

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

这与未指定的行为

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

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

例如,编译器不需要像这样在代码中记录计算顺序:

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

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

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

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

a是一个不确定的值,输出是未指定的 - 实际上,输出取决于之前存储在该特定RAM单元中的内容。我们称之为"垃圾值"。(在大喊"UB"之前,请参阅(为什么)正在使用未初始化的变量未定义的行为?

相关内容

  • 没有找到相关文章

最新更新