9

考虑以下关于反码体系结构的代码:

int zero = 0;
int negzero = -0;
std::cout<<(negzero < zero)<<std::endl;
std::cout<<(negzero <= zero)<<std::endl;
std::cout<<(negzero == zero)<<std::endl;
std::cout<<(~negzero)<<(~zero)<<std::endl;
std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;
  • 代码会产生什么输出?
  • 标准定义了哪些行,哪些行取决于实现,哪些行是未定义的行为?
4

3 回答 3

3

首先,你的第一个前提是错误的:

int negzero = -0;

应该在任何符合的架构上产生一个正常的零。

@101010 的回答中给出了相关参考:

3.9.1 基本类型 [basic.fundamental] §3:

... 有符号和无符号整数类型应满足 C 标准第 5.2.4.2.1 节中给出的约束。

稍后在 C 参考中:5.2.4.2.1 整数类型的大小

...前向引用:类型的表示(6.2.6)

和(仍然是 C): 6.2.6 类型的表示 / 6.2.6.2 整数类型 § 3

如果实现支持负零,它们只能通过以下方式生成:

  • &、|、^、~、<< 和 >> 运算符,其参数产生这样的值;

  • +、-、*、/ 和 % 运算符,其中一个参数为负零,结果为零;

  • 基于上述情况的复合赋值运算符。

所以negzero = -0不是这样的构造,不应产生负 0。

对于以下几行,我将假设负 0 是在支持它的实现上以按位方式产生的

C++ 标准根本不说负零,而 C 标准只是说它们的存在取决于实现。我找不到任何明确说明负零是否应该等于关系或相等运算符的正常零的段落。

所以我将在 C 参考文献中引用:6.5.8 关系运算符 §6

如果指定的关系为真,则每个运算符 <(小于)、>(大于)、<=(小于或等于)和 >=(大于或等于)应产生 1,如果是,则应产生 0 false.92) 结果的类型为 int。

并在 C++ 5.9 关系运算符 [expr.rel] §5

如果两个操作数(转换后)都是算术或枚举类型,则如果指定的关系为真,则每个运算符都应产生真,如果为假,则应产生假。

我对标准的解释是,实现可能允许整数值 0(负零)的替代表示,但它仍然是值 0 的表示,并且它应该在任何算术表达式中相应地执行,因为 C 6.2.6.2 整数类型§ 3 说:

负零 [...] 应仅由 [...] +、-、*、/ 和 % 运算符生成,其中一个参数为负零且结果为零

这意味着如果结果不为 0,则负 0 应该作为正常的 0 执行。

所以这两行至少是完美定义的,应该产生1

std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;

这条线被明确定义为依赖于实现:

std::cout<<(~negzero)<<(~zero)<<std::endl;

因为一个实现可能有填充位。如果没有填充位,则在一个补码体系结构~zero 上是 negzero,因此~negzero应该产生 a0但我无法在标准中找到负零是否应显示为0或 as -0。负浮点0 应该用减号显示,但对于整数负值似乎没有什么明确的。

对于涉及关系和相等运算符的最后 3 行,标准中没有任何明确的内容,所以我会说它是实现定义的

TL/DR:

依赖于实现:

std::cout<<(negzero < zero)<<std::endl;
std::cout<<(negzero <= zero)<<std::endl;
std::cout<<(negzero == zero)<<std::endl;
std::cout<<(~negzero)<<(~zero)<<std::endl;

完美定义,应产生 1:

std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;
于 2015-12-08T10:18:14.293 回答
3

根据我对标准的解释:

§3.9.1/p3 基本类型 [basic.fundamental]中的 C++ 标准实际上在 C 标准中抛出了球:

有符号和无符号整数类型应满足 C 标准第 5.2.4.2.1 节中给出的约束。

现在,如果我们转到 ISO/IEC 9899:2011 第 5.2.4.2.1 节,它将作为对§6.2.6.2/p2 整数类型的前向引用(Emphasis Mine):

如果符号位为零,则不应影响结果值。如果符号位为 1,则该值应通过以下方式之一进行修改:

  • 符号位为 0 的对应值取反(符号和幅度);

  • 符号位的值为 -(2^M)(二进制补码);

  • 符号位的值为 -(2^M - 1)(反码)。

其中哪一个适用是实现定义的,符号位为 1 且所有值位为零(对于前两个)或符号位和所有值位为 1(对于一个的补码)的值是否是陷阱表示或正常值。在符号和幅度以及一个的补码的情况下,如果这个表示是一个正常值,它被称为负零。

因此,负零的存在是实现定义的。

如果我们继续第 3 段:

如果实现支持负零,它们只能通过以下方式生成:

  • &、|、^、~、<< 和 >> 运算符,其操作数产生这样的值;

  • +、-、*、/ 和 % 运算符,其中一个操作数为负零且结果为零;

  • 基于上述情况的复合赋值运算符。

未指定这些情况实际上是生成负零还是正常零,以及当存储在对象中时负零是否变为正常零。

因此,未指定您显示的相关案例是否会产生负零。

现在进行第 4 段:

如果实现不支持负零,则 &、|、^、~、<< 和 >> 运算符的行为以及会产生此类值的操作数是未定义的。

因此,相关操作是否导致未定义的行为,取决于实现是否支持负零。

于 2015-12-08T08:18:29.260 回答
-2

首先,一个人的补码架构(甚至区分负零)相当罕见,这是有原因的。(硬件方面)添加二进制补码比添加一个补码基本上更容易。

您发布的代码似乎没有未定义的行为,甚至没有实现定义的行为,它可能不应该导致负零(或者它不应该与正常零区分开来)。

负零不应该那么容易产生(如果你设法做到这一点,它充其量是实现定义的行为)。如果它是一个补码架构,它们将由~0(按位反转)而不是-0.

C++ 标准对基本类型的行为的实际表示和要求相当模糊(这意味着规范只处理数字的实际含义)。这意味着您在将数字的内部表示与其实际值相关联时基本上不走运。因此,即使您做对了并使用了~0(或任何适合实施的方式),标准似乎仍然不会打扰表示,因为负零的值仍然为零。

#define zero (0)
#define negzero (~0)
std::cout<<(negzero < zero)<<std::endl;
std::cout<<(negzero <= zero)<<std::endl;
std::cout<<(negzero == zero)<<std::endl;
std::cout<<(~negzero)<<(~zero)<<std::endl;
std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;

前三行应该产生与negzero定义相同的输出zero。第三行应该输出两个零(因为标准要求0将其呈现为0没有符号符号)。最后两个应该输出一个。

在 C 标准中可以找到一些提示(关于如何产生负零),实际上提到了负零,但我认为没有提到它们应该比正常零进行比较。C 标准表明负零可能无法在对象中存储(这就是我在上面的示例中避免使用它的原因)。

C 和 C++ 相关的方式有理由认为在 C++ 中会以与在 C 中相同的方式产生负零,并且标准似乎允许这样做。虽然 C++ 标准允许其他方式(通过未定义的行为),但似乎没有其他方式可以通过定义的行为获得。所以可以肯定的是,如果 C++ 实现能够以合理的方式产生负零,它与类似的 C 实现相同。

于 2015-12-08T07:26:04.223 回答