为啥无符号短(乘)无符号短转换为有符号整数? [复制]

Posted

技术标签:

【中文标题】为啥无符号短(乘)无符号短转换为有符号整数? [复制]【英文标题】:Why is unsigned short (multiply) unsigned short converted to signed int? [duplicate]为什么无符号短(乘)无符号短转换为有符号整数? [复制] 【发布时间】:2016-02-17 07:15:14 【问题描述】:

为什么在 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) 或更大*

INT_MAX = 32767 (2^15-1) 或更大*

(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


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

【问题讨论】:

如果 int 在您的平台上是 16 位,那么您得到的结果不是 int。请注意您链接到的表中值的免责声明:“实际值取决于特定的系统和库实现,但应反映目标平台中这些类型的限制。” 您确定USHRT_MAXunsigned short 类型吗?在我的环境中(Lubuntu 下的 GCC 4.8 64 位),USHRT_MAX 实际上是int 类型(定义为(32767 * 2 + 1))。难怪USHRT_MAX*USHRT_MAX 会溢出。 有一个错字,我更正了。有什么改变吗? USHRT_MAX0xFFFF0xFFFF * 0xFFFF = 0xFFFE0001(没有溢出)这等于 4294836225-131071 所以这只是到 int 的最终转换,它把它扔掉了。 @BarmakShemirani INT_MAX0x7FFFFFFF,所以这实际上是溢出。 【参考方案1】:

您可能想阅读有关 implicit conversions 的内容,尤其是有关 numeric promotions 的部分

小整数类型的纯右值(例如char) 可以转换为较大整数类型的纯右值(例如int)。特别是,arithmetic operators 不接受小于int 的类型作为参数

上面所说的是,如果您在涉及arithmetic operators(当然包括乘法)的表达式中使用小于int(如unsigned short)的值,那么这些值将被提升为int

【讨论】:

这不是设计缺陷吗?特别是无符号类型的溢出是定义的行为,而有符号类型的溢出不是?我会理解charshort 被提升为签名int 但预计unsigned charunsigned short 将被提升为unsigned int 以允许定义的溢出......或者我在这里错了吗?分配 unsigned short 算术运算的签名 int 结果可以安全地转换为 unsigned short 而不会引起 UB? @SimonKraemer 也许是这样,但现在做任何事情都为时已晚。它起源于 C 的最初几天,任何小于 int 的整数都存储在一个 int 大小的寄存器中。 (可能这种行为来自 C 的前身之一) @M.M 我猜你是对的。我刚刚提出了另一个问题来专门分析这种行为:***.com/questions/33732489/…【参考方案2】:

这是常用的算术转换

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

C++11 §5/9:

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

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

C++11 §4.5/1:

boolchar16_tchar32_twchar_t 之外的整数类型的纯右值,其整数转换 rank (4.13) 小于int 的rank 可以转换为int 类型的prvalue 如果int 可以代表所有 源类型的值;否则,源纯右值可以转换为unsigned int类型的纯右值

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

【讨论】:

我认为 OP 在这里是安全的,因为如果 int 不能完全代表 short int 那么它(或者至少可以转换,但是你解释它)转换为 unsigned int,正如你在第二个中所写的那样标准报价。 @this:嗯,int 乘法可能会溢出,这在形式上是未定义的行为。编译器可能会利用这一点。本质上,编译器的程序员可以推断出它总是可以假设 UB 没有发生(因为如果它确实发生了,那么任何影响都是有效的行为),然后在一些晦涩的情况下会减少一两纳秒的令人费解的行为, 可能是在这种假设下进行优化的结果。【参考方案3】:

正如 Paolo M 在 cmets 中指出的那样,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,并发生定义明确的模乘。

【讨论】:

那么其他运营商呢?这个可以吗? ulong i = x + static_cast&lt;ulong&gt;(y)*m_mapSize.getX() 其中x,ygetX()unsigned short int i 的类型到底如何推断? @Slazer i 的类型是 ulong 因为你说了这么多。任何运算符的结果类型取决于它的两个操作数。在您的代码中,* 的操作数是ulongushort;根据提升规则,后者被提升为int,然后ulong 给出ulong 结果。那么+ 的操作数是ushortulong,所以ushort 最终被提升为ulong,结果是ulong【参考方案4】:

具有整数提升规则

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

【讨论】:

【参考方案5】:

似乎还没有人回答这部分问题:

我应该期待这个解决方案有任何问题吗?

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

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

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

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

【讨论】:

您还应该注意,虽然 int 在大多数当前平台上大于 short,但 C 标准并不保证它更大。 @plugwash 在 C 中,这是绝对有保证的。 ISO/IEC 9899:201x 6.2.5,第 8 段。 标准的那部分可能更清楚,但我很确定它的意思是“相同大小或更大”而不是“严格更大”。如果不是这样,那么我见过的每个 C 编译器都会不兼容。 @plugwash Quote: 整数转换等级较小的类型的值范围是其他类型值的子范围。 如果不够清楚,下一段准确地解释了子范围是什么。 @plugwash 我雇用你在 C++ 标准中找到类似的段落,因为这个问题是关于 C++ 而不是 C。也许 C++ 有不同的规则,但你确实只提到了 C。(你也许首先是指 C++,在第一条评论中?)【参考方案6】:

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

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

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

unsigned short c = t * 1U * t;

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

否则,整数提升将在两个操作数上执行。然后 以下规则适用于提升的操作数:

-如果两个操作数的类型相同,则不需要进一步转换。

-否则,如果两个操作数都具有带符号整数类型或都具有无符号 整数类型,具有较小整数转换等级类型的操作数是 转换为具有更高等级的操作数的类型。

-否则,如果具有无符号整数类型的操作数的秩大于或 等于另一个操作数的类型的等级,然后操作数与 有符号整数类型转换为无符号操作数的类型 整数类型。

【讨论】:

以上是关于为啥无符号短(乘)无符号短转换为有符号整数? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

有符号和无符号整型数据溢出问题

将“无符号字符”数组转换为“无符号短”数组的有效方法是啥?

如何使用缩放有效地将 16 位无符号短转换为 8 位无符号字符?

在C中将无符号短数组转换为字节数组

为啥 C 或 C++ 标准不明确将 char 定义为有符号或无符号?

当我想写出无符号短值时获取有符号短值