为什么unsigned short(乘)unsigned short转换为signed int?

Why is unsigned short (multiply) unsigned short converted to signed int?

为什么在 C++11 中 unsigned short * unsigned short 转换为 int

int 太小,无法处理最大值,如这行代码所示。

cout << USHRT_MAX * USHRT_MAX << endl;

MinGW 4.9.2 溢出

-131071

因为 (source)

USHRT_MAX = 65535 (2^16-1) or greater*

INT_MAX = 32767 (2^15-1) or greater*

(2^16-1)*(2^16-1) = ~2^32.


这个解决方案会不会有问题?

unsigned u = static_cast<unsigned>(t*t);

这个节目

unsigned short t;
cout<<typeid(t).name()<<endl;
cout<<typeid(t*t).name()<<endl;

给出输出

t
i

gcc version 4.4.7 20120313 (Red Hat 4.4.7-16) (GCC)
gcc version 4.8.2 (GCC)
MinGW 4.9.2

两者都

g++ p.cpp
g++ -std=c++11 p.cpp

这证明 t*t 在这些编译器上被转换为 int


有用的资源:

Signed to unsigned conversion in C - is it always safe?

Signed & unsigned integer multiplication

https://bytes.com/topic/c-sharp/answers/223883-multiplication-types-smaller-than-int-yields-int

http://www.cplusplus.com/reference/climits

http://en.cppreference.com/w/cpp/language/types


编辑:我已经在下图中演示了这个问题。

您可能想阅读有关 implicit conversions, especially the section about numeric promotions 的内容

Prvalues of small integral types (such as char) may be converted to prvalues of larger integral types (such as int). In particular, arithmetic operators do not accept types smaller than int as arguments

上面说的是,如果您在涉及 arithmetic operators(当然包括乘法)的表达式中使用小于 int 的值(如 unsigned short),则值将晋升为 int.

有整数提升规则

USHRT_MAX 值提升为 int。 然后我们做 2 int 的乘法(可能溢出)。

这是常用的算术转换

通常称为参数 promotion,尽管标准以更严格的方式使用该术语(合理的描述性术语与标准术语之间的永恒冲突)。

C++11 §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 […]

该段落继续描述细节,这相当于在更一般类型的阶梯上进行转换,直到可以表示所有参数。此阶梯上的最低梯级是二进制运算的两个操作数的 积分提升 ,因此至少会执行(但转换可以从更高的梯级开始)。而积分推广是这样开始的:

C++11 §4.5/1:

A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int

至关重要的是,这是关于类型,而不是算术表达式。在您的例子中,乘法运算符 * 的参数被转换为 int。然后乘法作为 int 乘法执行,产生 int 结果。

正如 Paolo M 在评论中指出的那样,USHRT_MAX 的类型为 int(这是由 5.2.4.2.1/1 指定的:所有此类宏的类型至少与int).

所以 USHRT_MAX * USHRT_MAX 已经是 int x int,没有促销发生。

这会在您的系统上调用有符号整数溢出,导致未定义的行为。


关于建议的解决方案:

unsigned u = static_cast<unsigned>(t*t);

这没有帮助,因为 t*t 本身会由于有符号整数溢出而导致未定义的行为。正如其他答案所解释的那样,由于历史原因,t 在乘法发生之前被提升为 int

您可以使用:

auto u = static_cast<unsigned int>(t) * t;

整数提升 之后,是 unsigned int 乘以 int;然后根据其余的常规算术转换,将int提升为unsigned int,并发生定义明确的模乘

正如其他答案所指出的,这是由于整数提升规则而发生的。

避免从等级较小的无符号类型转换为等级较高的有符号类型的最简单方法是确保转换为 unsigned int 而不是 int.

这是通过乘以 unsigned int 类型的值 1 来完成的。由于1是乘法恒等式,结果将保持不变:

unsigned short c = t * 1U * t;

首先计算操作数 t 和 1U。左操作数是有符号的,并且比无符号的右操作数具有更小的等级,因此它被转换为右操作数的类型。然后将操作数相乘,结果和剩余的右操作数也会发生相同的情况。下面引用的标准中的最后一段用于此促销。

Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:

-If both operands have the same type, then no further conversion is needed.

-Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.

-Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

好像还没有人回答这部分问题:

Should I expect any problems with this solution?

u = static_cast<unsigned>(t*t);

是的,这里有一个问题:它首先计算t*t并允许它溢出,然后将结果转换为unsigned。根据 C++ 标准,整数溢出会导致未定义的行为(即使它在实践中可能总是工作正常)。正确的解法是:

u = static_cast<unsigned>(t)*t;

注意第二个t在乘法前被提升为unsigned因为第一个操作数是unsigned.