28

考虑以下程序:

// http://ideone.com/4I0dT
#include <limits>
#include <iostream>

int main()
{
    int max = std::numeric_limits<int>::max();
    unsigned int one = 1;
    unsigned int result = max + one;
    std::cout << result;
}

// http://ideone.com/UBuFZ
#include <limits>
#include <iostream>

int main()
{
    unsigned int us = 42;
    int neg = -43;
    int result = us + neg;
    std::cout << result;
}

+ 运算符如何“知道”要返回的正确类型?int一般规则是将所有参数转换为最宽的类型,但这里在and之间没有明确的“赢家” unsigned int。在第一种情况下,unsigned int必须被选为 的结果operator+,因为我得到了 的结果2147483648。在第二种情况下,它必须选择int,因为我得到的结果是-1。然而,在一般情况下,我看不出这是如何确定的。这是我看到的未定义行为还是其他?

4

3 回答 3

40

这在 §5/9 中有明确概述:

许多期望算术或枚举类型的操作数的二元运算符会以类似的方式导致转换和产生结果类型。目的是产生一个通用类型,这也是结果的类型。这种模式称为通常的算术转换,其定义如下:

  • 如果任一操作数为 类型long double,则另一个应转换为long double
  • 否则,如果任一操作数为double,则另一个应转换为double
  • 否则,如果任一操作数为float,则另一个应转换为float
  • 否则,应在两个操作数上执行整数提升。
  • 然后,如果任一操作数是unsigned long另一个,则应转换为unsigned long.
  • 否则,如果一个操作数是 along int而另一个是unsigned int,则如果 along int可以表示 an 的所有值unsigned intunsigned int则应将 a 转换为 a long int;否则两个操作数都应转换为unsigned long int.
  • 否则,如果任一操作数为long,则另一个应转换为long
  • 否则,如果任一操作数为unsigned,则另一个应转换为unsigned

[注意:否则,唯一剩下的情况是两个操作数都是int]

在您的两种情况下,结果operator+都是unsigned. 因此,第二种情况实际上是:

int result = static_cast<int>(us + static_cast<unsigned>(neg));

因为在这种情况下, 的值us + neg不能用 表示int,所以 的值result是实现定义的——第 4.7/3 节:

如果目标类型是有符号的,则如果它可以在目标类型(和位域宽度)中表示,则该值不变;否则,该值是实现定义的。

于 2011-07-21T01:06:23.130 回答
12

在 C 标准化之前,编译器之间存在差异——一些遵循“保留值”规则,而另一些遵循“保留符号”规则。符号保留意味着如果任一操作数是无符号的,则结果是无符号的。这很简单,但有时会产生相当令人惊讶的结果(尤其是当负数转换为无符号时)。

C 对更复杂的“保值”规则进行了标准化。在值保留规则下,提升可以/确实取决于类型的实际范围,因此您可以在不同的编译器上得到不同的结果。例如,在大多数 MS-DOS 编译器上,int它的大小short与两者相同但long不同。在许多当前系统int上,它的大小与 相同longshort但两者都不同。使用价值保留规则,这些可能导致两者之间的提升类型不同。

值保留规则的基本思想是,如果它可以表示较小类型的所有值,它将提升为较大的有符号类型。例如,unsigned short可以将 16 位提升为 32 位signed int,因为 的每个可能值unsigned short都可以表示为signed int。当且仅当有必要保留较小类型的值时,类型将被提升为无符号类型(例如,如果unsigned shortsigned int都是 16 位,则 asigned int不能表示 的所有可能值unsigned short,因此unsigned short将提升为unsigned int)。

当您按原样分配结果时,无论如何,结果都会转换为目标类型,因此其中大部分差异相对较小-至少在大多数典型情况下,它只会将位复制到结果中,并且由您决定是否将其解释为已签名或未签名。

当您分配结果(例如比较)时,事情可能会变得非常丑陋。例如:

unsigned int a = 5;
signed int b = -5;

if (a > b)
    printf("Of course");
else
    printf("What!");

在符号保留规则下,b将被提升为无符号,并在此过程中变为等于UINT_MAX - 4,所以“什么!” if将采取的腿。使用保值规则,您也可以设法产生一些有点像这样的奇怪结果,但是 1) 主要在与 DOSint大小相同的类似 DOS 的系统上short,以及 2) 无论如何通常更难做到这一点。

于 2011-07-21T01:24:45.753 回答
2

它选择您将结果放入的任何类型,或者至少 cout 在输出期间尊重该类型。

我不记得了,但我认为 C++ 编译器为两者生成相同的算术代码,它只是比较和输出关心符号。

于 2011-07-21T01:04:07.437 回答