在零补充架构上负零的行为?

时间:2015-12-08 05:57:08

标签: c++ c++11 integer standards ones-complement

在一个补码架构上考虑以下代码:

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;
  • 代码会产生什么输出?
  • 标准定义了哪些行,哪些行依赖于实现,哪些行是未定义的行为?

3 个答案:

答案 0 :(得分:3)

根据我对标准的解释:

§3.9.1/ p3中的C ++标准基本类型[basic.fundamental] 实际上在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(对于补码),是a   陷阱表示或正常值。 在签名和。的情况下   如果这种表示是正常的,那么幅度和一个补码   它被称为负零。

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

如果我们在第3段继续进行:

  

如果实现支持负零,则应生成它们   只有:

     
      
  • &amp;,|,^,〜,&lt;&lt;&lt;&gt;&gt;具有操作数的操作符   值;

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

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

  •   
     

未指明这些案例是否实际产生负面影响   零或正常零,以及负零是否变为正常   存储在对象中时为零。

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

现在进行第4段:

  

如果实现不支持负零,则表示行为   &amp;,|,^,〜,&lt;&lt;&lt;&gt;&gt;具有操作数的运算符   这样的值是未定义的。

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

答案 1 :(得分: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

  

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

     
      
  • &amp;,|,^,〜,&lt;&lt;&lt;&gt;&gt;具有产生这种值的参数的运算符;

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

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

  •   

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

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

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

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

  

每个运营商&lt; (小于),&gt; (大于),&lt; =(小于或等于),&gt; =   (大于或等于)如果指定的关系为真,则应为1,如果为假,则为0 .92)   结果的类型为int。

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

  

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

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

  

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

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

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

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

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

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

因为实现可能有填充位。如果没有填充位,在一个补码架构上~zero negzero,那么~negzero应生成一个0,但我找不到标准,如果负零显示为0-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;

答案 2 :(得分:-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实现相同。