当二元运算符两侧的符号不同时,提升规则如何工作?

发布于 11-25 11:37 字数 789 浏览 2 评论 0原文

考虑以下程序:

// 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 和 unsigned int 之间没有明显的“赢家”。在第一种情况下,必须选择 unsigned int 作为 operator+ 的结果,因为我得到的结果是 2147483648。在第二种情况下,它必须选择int,因为我得到的结果是-1。但我不知道在一般情况下这是如何确定的。这是我看到的未定义行为还是其他什么?

Consider the following programs:

// 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;
}

and

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

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

How does the + operator "know" which is the correct type to return? The general rule is to convert all of the arguments to the widest type, but here there's no clear "winner" between int and unsigned int. In the first case, unsigned int must be being chosen as the result of operator+, because I get a result of 2147483648. In the second case, it must be choosing int, because I get a result of -1. Yet I don't see in the general case how this is decidable. Is this undefined behavior I'm seeing or something else?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

恰似旧人归2024-12-02 11:37:05

§5/9 中明确概述了这一点:

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

  • 如果任一操作数的类型为 long double,则另一个操作数应转换为 long double
  • 否则,如果其中一个操作数为 double,则另一个操作数应转换为 double
  • 否则,如果其中一个操作数为 float,则另一个操作数应转换为 float
  • 否则,将对两个操作数执行积分提升。
  • 然后,如果其中一个操作数为 unsigned long,则另一个操作数应转换为 unsigned long
  • 否则,如果一个操作数是long int,另一个操作数是unsigned int,那么如果long int可以表示unsigned intunsigned int 应转换为 long int;否则两个操作数都应转换为unsigned long int
  • 否则,如果其中一个操作数为 long,则另一个操作数应转换为 long
  • 否则,如果其中一个操作数是无符号,则另一个操作数应转换为无符号

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

在这两种情况下,operator+ 的结果是 未签名。因此,第二种情况是有效的:

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

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

如果目标类型是带符号的,并且可以用目标类型(和位字段宽度)表示,则该值不会改变;否则,该值是实现定义的。

This is outlined explicitly in §5/9:

Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:

  • If either operand is of type long double, the other shall be converted to long double.
  • Otherwise, if either operand is double, the other shall be converted to double.
  • Otherwise, if either operand is float, the other shall be converted to float.
  • Otherwise, the integral promotions shall be performed on both operands.
  • Then, if either operand is unsigned long the other shall be converted to unsigned long.
  • Otherwise, if one operand is a long int and the other unsigned int, then if a long int can represent all the values of an unsigned int, the unsigned int shall be converted to a long int; otherwise both operands shall be converted to unsigned long int.
  • Otherwise, if either operand is long, the other shall be converted to long.
  • Otherwise, if either operand is unsigned, the other shall be converted to unsigned.

[Note: otherwise, the only remaining case is that both operands are int]

In both of your scenarios, the result of operator+ is unsigned. Consequently, the second scenario is effectively:

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

Because in this case the value of us + neg is not representable by int, the value of result is implementation-defined – §4.7/3:

If the destination type is signed, the value is unchanged if it can be represented in the destination type (and bit-field width); otherwise, the value is implementation-defined.

冷情2024-12-02 11:37:05

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

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

值保留规则的基本思想是,如果可以表示较小类型的所有值,它将提升为更大的有符号类型。例如,16 位 unsigned Short 可以提升为 32 位 signed int,因为 unsigned Short 的每个可能值都可以表示为signed int。当且仅当有必要保留较小类型的值时(例如,如果unsigned Shortsigned int都是16位,这些类型才会被提升为无符号类型,那么 signed int 无法表示 unsignedshort 的所有可能值,因此 unsignedshort 将提升为 unsignedint )。

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

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

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

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

在符号保留规则下,b 将提升为无符号,并在此过程中变得等于 UINT_MAX - 4,因此“What!”将采用 if 的一段。通过值保留规则,您可以设法产生一些类似于这样的奇怪结果,但是1)主要在类DOS系统上,其中int大小相同作为short,并且2)无论如何通常都更难做到。

Before C was standardized, there were differences between compilers -- some followed "value preserving" rules, and others "sign preserving" rules. Sign preserving meant that if either operand was unsigned, the result was unsigned. This was simple, but at times gave rather surprising results (especially when a negative number was converted to an unsigned).

C standardized on the rather more complex "value preserving" rules. Under the value preserving rules, promotion can/does depend on the actual ranges of the types, so you can get different results on different compilers. For example, on most MS-DOS compilers, int is the same size as short and long is different from either. On many current systems int is the same size as long, and short is different from either. With value preserving rules, these can lead to the promoted type being different between the two.

The basic idea of value preserving rules is that it'll promote to a larger signed type if that can represent all the values of the smaller type. For example, a 16-bit unsigned short can be promoted to a 32-bit signed int, because every possible value of unsigned short can be represented as a signed int. The types will be promoted to an unsigned type if and only if that's necessary to preserve the values of the smaller type (e.g., if unsigned short and signed int are both 16 bits, then a signed int can't represent all possible values of unsigned short, so an unsigned short will be promoted to unsigned int).

When you assign the result as you have, the result will get converted to the destination type anyway, so most of this makes relatively little difference -- at least in most typical cases, where it'll just copy the bits into the result, and it's up to you to decide whether to interpret that as signed or unsigned.

When you don't assign the result such as in a comparison, things can get pretty ugly though. For example:

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

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

Under sign preserving rules, b would be promoted to unsigned, and in the process become equal to UINT_MAX - 4, so the "What!" leg of the if would be taken. With value preserving rules, you can manage to produce some strange results a bit like this as well, but 1) primarily on the DOS-like systems where int is the same size as short, and 2) it's generally harder to do it anyway.

Hello爱情风2024-12-02 11:37:05

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

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

It's choosing whatever type you put your result into or at least cout is honoring that type during output.

I don't remember for sure but I think C++ compilers generate the same arithmetic code for both, it's only compares and output that care about sign.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文