将两个32位整数相加可能导致整数溢出:
uint64_t u64_z = u32_x + u32_y;
如果将32位整数之一先转换或加到64位整数中,则可以避免此溢出。
uint64_t u64_z = u32_x + u64_a + u32_y;
然而,如果编译器决定重新排列加法:
uint64_t u64_z = u32_x + u32_y + u64_a;
整数溢出仍可能发生。
编译器是否允许进行这样的重新排序,或者我们可以相信它们会注意到结果的不一致并保持表达式顺序不变?
将两个32位整数相加可能导致整数溢出:
uint64_t u64_z = u32_x + u32_y;
如果将32位整数之一先转换或加到64位整数中,则可以避免此溢出。
uint64_t u64_z = u32_x + u64_a + u32_y;
然而,如果编译器决定重新排列加法:
uint64_t u64_z = u32_x + u32_y + u64_a;
整数溢出仍可能发生。
编译器是否允许进行这样的重新排序,或者我们可以相信它们会注意到结果的不一致并保持表达式顺序不变?
如果优化器进行这样的重新排序,它仍然受制于C规范,因此这样的重新排序将变为:
uint64_t u64_z = (uint64_t)u32_x + (uint64_t)u32_y + u64_a;
理由:
我们从这里开始
uint64_t u64_z = u32_x + u64_a + u32_y;
加法是从左到右进行的。
整数提升规则指出,在原始表达式中的第一次加法中,u32_x
将被提升为 uint64_t
。在第二次加法中,u32_y
也将被提升为 uint64_t
。
因此,为了符合 C 规范,任何优化器都必须将 u32_x
和 u32_y
提升为 64 位无符号值。这相当于添加强制类型转换。(实际的优化不是在 C 级别上完成的,但我使用 C 表示法是因为那是我们理解的表示法。)
(u32_x + u32_t) + u64_a
吧? - Useless编译器只允许根据“仿佛”规则重新排序。也就是说,如果重新排序总是得到与指定顺序相同的结果,则是被允许的。否则(就像您的示例一样),不行。
例如,给定以下表达式:
i32big1 - i32big2 + i32small
这个语句被精心构建,可以对已知相似但较大的两个值进行减法计算,然后再加上另一个小的值(从而避免任何溢出),编译器可能会重新排序为:
(i32small - i32big2) + i32big1
而且依靠目标平台使用二补数算术并进行包装以防止问题。(如果编译器已经把i32small放在寄存器中,那么这样的重新排序可能是明智的。)
i32big1 - i32big2 + i32small
暗示着有符号类型。还有其他相关问题需要考虑。 - chux - Reinstate Monica(i32small-i32big2) + i32big1
(因为它可能会导致UB),但编译器可以重新排列它以实现有效性,因为编译器可以确信行为将是正确的。 - Martin Bonner supports Monicaif (a + 1 < a)
进行优化。 - csiz引用自标准:
[ Note: Operators can be regrouped according to the usual mathematical rules only where the operators really are associative or commutative.7 For example, in the following fragment int a, b;
/∗ ... ∗/ a = a + 32760 + b + 5;
the expression statement behaves exactly the same as
a = (((a + 32760) + b) + 5);
due to the associativity and precedence of these operators. Thus, the result of the sum (a + 32760) is next added to b, and that result is then added to 5 which results in the value assigned to a. On a machine in which overflows produce an exception and in which the range of values representable by an int is [-32768,+32767], the implementation cannot rewrite this expression as
a = ((a + b) + 32765);
since if the values for a and b were, respectively, -32754 and -15, the sum a + b would produce an exception while the original expression would not; nor can the expression be rewritten either as
a = ((a + 32765) + b);
or
a = (a + (b + 32765));
since the values for a and b might have been, respectively, 4 and -8 or -17 and 12. However on a machine in which overflows do not produce an exception and in which the results of overflows are reversible, the above expression statement can be rewritten by the implementation in any of the above ways because the same result will occur. — end note ]
std::common_type
,然后再进行加法运算 - 这样是安全的,不依赖于任何参数顺序或手动转换,但是它看起来很笨重。uint64_t
,以便与最右边的值相加。 - Uselessu64_a = 0; u32_x = 1; u32_y = 0xFFFFFFFF;
uint64_t u64_z = u32_x + u64_a + u32_y;
uint64_t u64_z = u32_x + u32_y + u64_a; // u32_x + u32_y carry does not add to sum.
unsigned >= 34
位)。整数提升导致u32_x + u32_y
加法在64位数学中发生。顺序无关紧要。unsigned == 33
位)。整数提升导致加法在有符号33位数学中发生,有符号溢出是UB。// Same
u32_x + u64_a + u32_y;
u64_a + u32_x + u32_y;
u32_x + (uint64_t) u32_y + u64_a;
...
// Same as each other below, but not the same as the 3 above.
uint64_t u64_z = u32_x + u32_y + u64_a;
uint64_t u64_z = u64_a + (u32_x + u32_y);
...我们能相信他们会注意到结果的不一致性并保持表达式顺序吗?
可以信任,但是OP的编程目标并不十分清晰。是否应该包含u32_x + u32_y
的进位贡献?如果OP想要这种贡献,代码应该是这样的。
uint64_t u64_z = u64_a + u32_x + u32_y;
uint64_t u64_z = u32_x + u64_a + u32_y;
uint64_t u64_z = u32_x + (u32_y + u64_a);
uint64_t u64_z = u32_x + u32_y + u64_a;
uint32_t
的值 - 它们不会发生溢出,而是会进行包裹。这些不是不同的行为。 - Martin Bonner supports Monica((uint32_t)-1 + (uint32_t)1) + (uint64_t)0
的结果为0
,而(uint32_t)-1 + ((uint32_t)1 + (uint64_t)0)
的结果为0x100000000
,这两个值不相等。因此,编译器能否进行这种转换非常重要。但是,标准只针对有符号整数使用了“溢出”一词,而未使用该术语来描述无符号整数。 - Steve Jessop