我试图理解算术溢出。假设我有以下内容,
unsigned long long x;
unsigned int y, z;
x = y*z;
y*z 可能导致整数溢出。将其中一个操作数转换为 unsigned long long 是否可以缓解此问题。64 位操作数与 32 位操作数相乘的预期结果是什么?
我试图理解算术溢出。假设我有以下内容,
unsigned long long x;
unsigned int y, z;
x = y*z;
y*z 可能导致整数溢出。将其中一个操作数转换为 unsigned long long 是否可以缓解此问题。64 位操作数与 32 位操作数相乘的预期结果是什么?
您显然假设这unsigned int
是 32 位和unsigned long long
64 位。他们不必是,让我们假设这一点。
通过转换 32 位操作数获得的 64 位操作数仍然适合 32 位。因此,在 中y*(unsigned long long)z
,每个操作数首先被提升为unsigned long long
,结果被计算为一个unsigned long long
并且不能“溢出”,因为它是两个量化的乘积,每个量化都适合 32 位。
(此外,在 C 标准的词汇表中,无符号操作不会“溢出”。溢出是在目标类型范围之外产生结果的未定义行为。无符号操作所做的是“环绕”)。
unsigned long long x;
unsigned int y, z;
x = y*z;
表达式y*z
的计算不受它出现的上下文的影响。它将两个值相乘unsigned int
,产生一个unsigned int
结果。如果数学结果不能表示为一个unsigned int
值,则结果将回绕。然后,赋值隐式地将(可能被截断的)结果从unsigned int
转换为unsigned long long
。
如果您想要产生unsigned long long
结果的乘法,则需要显式转换一个或两个操作数:
x = (unsigned long long)y * z;
或者,更明确地说:
x = (unsigned long long)y * (unsigned long long)z;
C 的*
乘法运算符仅适用于相同类型的两个操作数。因此,当您给它不同类型的操作数时,它们会在执行乘法之前转换为某种常见类型。当您混合有符号和无符号类型时,规则可能有点复杂,但在这种情况下,如果您将 an 乘以unsigned long long
,unsigned int
则unsigned int
操作数将提升为unsigned long long
。
如果 unsigned long long
在大多数系统上至少是 的两倍unsigned int
,那么结果既不会溢出也不会回绕,因为例如,64 位unsigned long long
可以保存任意两个 32 位unsigned int
值相乘的结果。但是,如果你在一个系统上,例如,int
并且long long
都是 64 位宽,你仍然可以有溢出环绕,给你一个x
不等于和的数学乘积的y
结果z
。
如果一个操作数比另一个宽,编译器应该(或表现得好像是这样)将两个操作数转换为相同的大小,因此将一个操作数转换为更大的大小将产生正确的行为。
这是在 C 和 C++ 标准中指定的。C++11 标准(n3337 草案)在第五章声明 9 中有这样的说法:
...如果两个操作数都具有有符号整数类型或都具有无符号整数类型,则具有较小整数转换等级的类型的操作数应转换为具有较大等级的操作数的类型。
有几页描述了所有的转换和发生的事情,但这就是定义这个特定表达式的行为的原因。