我试图理解算术溢出。假设我有以下内容,
unsigned long long x;
unsigned int y, z;
x = y*z;
y * z会导致整数溢出。将其中一个操作数强制转换为unsigned long long可以缓解此问题。 64位操作数与32位操作数相乘的预期结果是什么?
答案 0 :(得分:5)
您显然假设unsigned int
是32位且unsigned long long
是64位。它们不一定是,让我们假设这一点。
通过转换32位操作数获得的64位操作数仍然适合32位。因此,在y*(unsigned long long)z
中,每个操作数首先被提升为unsigned long long
,结果计算为unsigned long long
并且不能“溢出”,因为它是两个量化的乘法每个32位。
(另外,在C标准的词汇表中,无符号运算不会“溢出”。溢出是在目标类型的边界之外产生结果的未定义行为。无符号运算的作用是“环绕”)。
答案 1 :(得分:4)
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的*
乘法运算符仅将 应用于两个相同类型的操作数。因此,当你给它不同类型的操作数时,它们会在执行乘法之前转换为某种常见类型。当您混合有符号和无符号类型时,规则可能会有点复杂,但在这种情况下,如果将unsigned long long
乘以unsigned int
,则unsigned int
操作数将提升为{{ 1}}。
如果 unsigned long long
的宽度至少是unsigned long long
的两倍,就像在大多数系统上一样,那么结果既不会溢出也不会回绕,因为,例如,64位unsigned int
可以保存任意两个32位unsigned long long
值的乘法结果。但是,如果你所在的系统,例如unsigned int
和int
都是64位宽,你仍然可以有 overflow 环绕,在{中给你一个结果{1}}这不等于long long
和x
的数学乘积。
答案 2 :(得分:3)
如果一个操作数比另一个操作数宽,则编译器应该(或表现得好像)将两个操作数转换为相同的大小,因此将一个操作数转换为更大的操作数将产生正确的行为。
这在C和C ++标准中规定。 C ++ 11标准(n3337草案)在第五章中有说法9:
...如果两个操作数都有有符号整数类型或两者都有无符号 整数类型,具有较小整数转换类型的操作数 rank应转换为具有更高等级的操作数的类型。
有几页描述了所有的转换和内容,但这就是定义这个特定表达式行为的原因。