当二元运算符两边的签名不同时,促销规则如何工作?

Bil*_*eal 25 c++ arithmetic-expressions overflow integer-promotion

考虑以下程序:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

+运算符如何"知道"返回哪个正确的类型?一般的规则是把所有的参数转换为最广泛的类型,但在这里没有明确的"赢家"之间intunsigned int.在第一种情况下,unsigned int必须选择作为结果operator+,因为我得到了结果2147483648.在第二种情况下,它必须选择int,因为我得到了结果-1.然而,在一般情况下,我没有看到这是如何可判定的.这是我看到的未定义的行为还是其他什么?

ild*_*arn 35

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

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

  • 如果任一操作数是类型long double,则另一个操作数应转换为long double.
  • 否则,如果任一操作数是double,则另一个操作数应转换为double.
  • 否则,如果任一操作数是float,则另一个操作数应转换为float.
  • 否则,应对两个操作数执行整体促销.
  • 然后,如果任一操作数是unsigned long另一个应转换为unsigned long.
  • 否则,如果一个操作数是a long int和另一个unsigned int,那么如果a long int可以表示a的所有值unsigned int,unsigned int则应转换为a long int; 否则两个操作数都应转换为unsigned long int.
  • 否则,如果任一操作数是long,则另一个操作数应转换为long.
  • 否则,如果任一操作数是unsigned,则另一个操作数应转换为unsigned.

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

在两个场景中,结果operator+都是unsigned.因此,第二种情况是有效的:

int result = static_cast<int>(us + static_cast<unsigned>(neg));
Run Code Online (Sandbox Code Playgroud)

因为在这种情况下,值us + neg不能表示int,值result是实现定义的 - §4.7/ 3:

如果目标类型已签名,则如果可以在目标类型(和位字段宽度)中表示该值,则该值不会更改; 否则,该值是实现定义的.

  • @Billy:它是实现定义的,而不是UB(4.7/3). (5认同)
  • @Billy:你对第二个问题的分析是有缺陷的.`us`和`neg`都转换为unsigned,总共产生'UINT_MAX`.然后,您的实现选择将此值转换为`int`为`-1`.如果你想看到表达式'us + neg`的值,那么执行`std :: cout <<(us + neg)`,在打印它之前不要将值强制转换为`int`. (3认同)
  • 你基本上是正确的,原则上它可能在其他地方不起作用.一个实现可以定义将任何大于`INT_MAX`的`unsigned int`值转换为`int`会导致值为0.它只是不会崩溃. (3认同)
  • @Billy:`std :: cout << static_cast <int>(us + static_cast <unsigned>(neg));`也会打印`-1`.为什么你不期望它?(或者你可能在我第一次编辑之前问过,在这种情况下,忽略这一点: - ]) (2认同)
  • @Darren:出于实际目的,不,没有。但我认为这是代码卫生方面的一项有价值的练习,了解您是否以及如何依赖实现定义的行为。这并不是真正关于 2 的补码 - 允许 2 的补码实现进行饱和有符号算术甚至饱和转换。所以`(int)(unsigned)(-1)` 将是`INT_MAX`。但这并不是关于它是否真的会发生,而是你的代码是否需要被标记为“不完全可移植”,以便警告拥有特殊机器的怪人;-)。 (2认同)

Jer*_*fin 12

在C标准化之前,编译器之间存在差异 - 一些遵循"保值"规则,另一些遵循"签署保留"规则.保留符号意味着如果任一操作数是无符号的,则结果是无符号的.这很简单,但有时会给出相当惊人的结果(特别是当负数转换为无符号时).

C对更复杂的"保值"规则进行了标准化.在保值规则下,促销可以/确实取决于类型的实际范围,因此您可以在不同的编译器上获得不同的结果.例如,在大多数MS-DOS编译器上,它们int的大小shortlong它们的大小相同.在许多当前系统int上,它们的大小与之相同long,并且short两者都不同.使用保值规则,这些可以导致促销类型在两者之间不同.

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

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

当你没有分配比较结果时,事情会变得非常难看.例如:

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

if (a > b)
    printf("Of course");
else
    printf("What!");
Run Code Online (Sandbox Code Playgroud)

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

  • "C标准化了相当复杂的"值保留"规则","在符号保留规则下,b将被提升为无符号,并且在此过程中变得等于UINT_MAX - 4,所以if的"什么!"之后被采取".但是在标准C++中,`b`*被*提升为无符号,而"什么!" 腿*是*被采取.我认为在某个地方出现了错误的方法. (2认同)