此问题是另一个SO question的结果。
示例代码
#include <iostream>
int main()
{
unsigned long b = 35000000;
int i = 100;
int j = 30000000;
unsigned long n = ( i * j ) / b; // #1
unsigned long m = ( 100 * 30000000 ) / b; // #2
std::cout << n << std::endl;
std::cout << m << std::endl;
}
输出
85
85
使用g++ -std=c++11 -Wall -pedantic -O0 -Wextra
编译此代码会发出以下警告:
9:28: warning: integer overflow in expression [-Woverflow]
问题
我认为#1
和#2
调用未定义的行为是正确的,因为中间结果100 * 30000000
不适合int
?或者我看到明确定义的输出?
为什么我只收到#2
的警告?
答案 0 :(得分:3)
是的,它是未定义的行为,如果unsigned long
是64位类型,则得到的结果通常是不同的。
¹这是UB,所以没有保证。
答案 1 :(得分:2)
1)是的,这是未定义的行为。
2)因为#1涉及变量(不是常量),所以编译器一般不知道它是否会溢出(虽然在这种情况下它会发生,但我不知道为什么它不会发出警告)。
答案 2 :(得分:2)
是的,这是未定义的行为。如果你刚刚停在那儿return m
怎么办?编译器需要从A点到达B点,并且您已经告诉它通过进行计算(这是不可能的)来完成它。编译器可以选择优化此语句,使您不会出现溢出,但据我所知,该标准不需要优化器执行任何操作。
您明确告诉gcc根本不进行优化(-O0
),所以我的假设是它在那时不知道i
和j
的值。通常你会因为constant folding而学习这些数值,但就像我说的那样,你告诉它不要优化。
如果你重新运行它并且它仍然没有提及它,那么也有可能在优化器运行之前生成此警告,因此在这一步骤中完全不能进行常量折叠。
答案 3 :(得分:1)
你得到两个警告,因为编译器知道操作数中的值。输出是正确的,因为它们都使用无符号长/b
。要被b
整除的临时值必须保持大于或等于数据类型范围,( i * j )
或( 100 * 30000000 )
存储在CPU寄存器中,该寄存器具有要分割的值的相同数据类型范围,如果b
为int
,则临时结果为int
,因为b
为ulong,int不能除以ulong,临时值存储到了ulong。
如果它溢出则是未定义的行为,但在这些情况下它不会溢出
具有相同结构的程序,只需将b
更改为int
,在.s代码上只有两行。
cltd
idivl (%ecx)
到b = int
movl $0,
%edx divl (%ecx)
到b =无符号长,
idivl执行签名分割,将值存储为签名
divl执行无符号除法,将值存储为无符号
所以你是对的,操作确实溢出,输出是正确的,因为除法操作。
答案 4 :(得分:0)
对于5/4,结果是未定义的行为。
但请注意,如果您将类型更改为无符号(对于常量只添加u
后缀),不仅值适合,而且根据3.9.1 / 4,算术成为模运算和即使对于不适合该类型的较大中间值,结果也是完美定义的。