将临时值存储为某种数据类型时,算术运算的标准规则是什么?

What is the standard rule on arithmetic operations when storing temporary values to a certain data type?

我有一些很久以前写的代码(当时我使用的是 visual studio 2003)。现在我正在使用 gcc 并且一些值溢出了,我看了一下发生了什么,这让我有点吃惊。

让我展示一下发生了什么:

有效(输出 = 85):

int b = 35000000;
unsigned long a = 30000000;
unsigned long n = ( 100 * a ) / b;

不起作用(溢出):

int b = 35000000;
int a = 30000000;
unsigned long n = ( 100 * a ) / b;

不起作用(溢出):

int b = 35000000;
unsigned long n = ( 100 * 30000000 ) / b;

这应该都是正确的。现在让我烦恼的是:

unsigned long b= 35000000;
unsigned long n = ( 100 * 30000000 ) / b;

曾经工作过!现在没有了。

嗯,实际上它仍然适用于 Microsoft 编译器,但不适用于 clang 和 gcc。如果您愿意,请继续使用不同的编译器编译它:http://rextester.com/BZU89042

关于这些的标准 C++ 规则是什么?

确定整数文字类型的规则是,来自[lex.icon]

The type of an integer literal is the first of the corresponding list in Table 7 in which its value can be represented.

其中,没有后缀,类型列表是 int,然后是 long int,然后是 long long int。之后我们做数学的时候,规则总是"the usual arithmetic conversions,",枚举在[expr]:

This pattern is called the usual arithmetic conversions, which are defined as follows:

(11.1) If either operand is of scoped enumeration type, no conversions are performed; if the other operand does not have the same type, the expression is ill-formed.

(11.2) If either operand is of type long double, the other shall be converted to long double.

(11.3) Otherwise, if either operand is double, the other shall be converted to double.

(11.4) Otherwise, if either operand is float, the other shall be converted to float.

(11.5) Otherwise, the integral promotions shall be performed on both operands.63 Then the following rules shall be applied to the promoted operands:

(11.5.1) If both operands have the same type, no further conversion is needed.

(11.5.2) Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank shall be converted to the type of the operand with greater rank.

(11.5.3) Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type.

(11.5.4) Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, the operand with unsigned integer type shall be converted to the type of the operand with signed integer type.

(11.5.5) Otherwise, both operands shall be converted to the unsigned integer type corresponding to the type of the operand with signed integer type.


让我们看一下您的示例:

int b = 35000000;
unsigned long a = 30000000;
unsigned long n = ( 100 * a ) / b;

这很好,因为 100 * a 的类型是 unsigned long(因为 11.5.3),它足够宽以容纳该结果。

其余的不起作用,因为乘法产生了一个类型 int。两个 int 相乘产生一个 int(因为 11.5.1),在第一种情况下,我们明确声明 aint,在其余情况下在文字 30000000 具有类型 int 的情况下(因为它足够小,可以用 int 表示)

请注意,在最后一个示例中:

unsigned long b= 35000000;
unsigned long n = ( 100 * 30000000 ) / b;

声明bn并不重要unsigned long,表达式(100 * 30000000)仍然是两个int相乘,并且类型 int 也是如此,无论表示形式如何。 gcc 和 clang 都警告这个溢出。

要修复它,您可以随时为文字添加后缀。在这种情况下,100u30000000u 都可以。这使得文字类型为 unsigned int(根据 [lex.icon]),这使得乘法的类型为 unsigned int(根据 [expr]/11.5.3),它不会溢出。

发挥作用的标准规则是:

  1. 各种整数类型的保证最小范围,特别是 signed int 和 unsigned long。在这种情况下,实际范围取决于编译器和目标平台。针对 x86 的 MSVC 将提供 [-2^31..2^31) 用于有符号整数,[0..2^32) 用于无符号长整数。针对 Windows 以外的 64 位平台的 gcc 和 clang 将为有符号整数提供相同的范围,但为无符号长整数提供 [0..2^64)。

  2. 有符号整数溢出引发未定义行为的事实 [第 3.9.1 节,第 4 段]。如果重组不影响结果,则允许编译器根据普通的交换和关联规则重组操作。 [第 1.9 节] 考虑到一旦发生有符号整数溢出,结果可以是任何东西,这个标准可能比您意识到的要低。

  3. "usual arithmetic conversions," 具体来说:

[I]f the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type. [Section 5.]

举个例子:

unsigned long b = 35000000;
unsigned long n = ( 100 * 30000000 ) / b;

在子表达式100 * 30000000中,两个操作数都是signed int。当你将 3000 万乘以 100 时,你将得到 30 亿。这是有符号 32 位整数的溢出,因此您调用了未定义的行为。

明显的事情,这显然是 MSVC 所做的,就是将 32 位模式视为二进制补码,产生 -1294967296。所以现在我们有 -1294967296 / 35000000ul。通常的算术转换规则告诉我们将 signed int 转换为 unsigned long。使用 MSVC,这会返回 30 亿,并且整数除法会得到预期的 85。

gcc 或 clang 有一些不同之处。首先,您的目标可能是 64 位非 Windows 平台,这意味着 unsigned long 是 64 位宽而不是 32 位。其次,这些编译器可能会通过依赖 "legal fiction" 不会有任何有符号整数溢出。第三,无论溢出的结果现在将在除法之前转换为更宽的无符号类型。