如果表达式的中间结果溢出,它是未定义的行为吗?

时间:2012-09-19 04:19:29

标签: c++ integer-overflow

此问题是另一个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. 我认为#1#2调用未定义的行为是正确的,因为中间结果100 * 30000000不适合int?或者我看到明确定义的输出?

  2. 为什么我只收到#2的警告?

5 个答案:

答案 0 :(得分:3)

是的,它是未定义的行为,如果unsigned long是64位类型,则得到的结果通常是不同的。

¹这是UB,所以没有保证。

答案 1 :(得分:2)

1)是的,这是未定义的行为。

2)因为#1涉及变量(不是常量),所以编译器一般不知道它是否会溢出(虽然在这种情况下它会发生,但我不知道为什么它不会发出警告)。

答案 2 :(得分:2)

中级结果

是的,这是未定义的行为。如果你刚刚停在那儿return m怎么办?编译器需要从A点到达B点,并且您已经告诉它通过进行计算(这是不可能的)来完成它。编译器可以选择优化此语句,使您不会出现溢出,但据我所知,该标准不需要优化器执行任何操作。

为什么变量时没有错误?

您明确告诉gcc根本不进行优化(-O0),所以我的假设是它在那时不知道ij的值。通常你会因为constant folding而学习这些数值,但就像我说的那样,你告诉它不要优化。

如果你重新运行它并且它仍然没有提及它,那么也有可能在优化器运行之前生成此警告,因此在这一步骤中完全不能进行常量折叠。

答案 3 :(得分:1)

你得到两个警告,因为编译器知道操作数中的值。输出是正确的,因为它们都使用无符号长/b。要被b整除的临时值必须保持大于或等于数据类型范围,( i * j )( 100 * 30000000 )存储在CPU寄存器中,该寄存器具有要分割的值的相同数据类型范围,如果bint,则临时结果为int,因为b为ulong,int不能除以ulong,临时值存储到了ulong。

如果它溢出则是未定义的行为,但在这些情况下它不会溢出

具有相同结构的程序,只需将b更改为int,在.s代码上只有两行。

cltd 
idivl   (%ecx) 

到b = int

movl    $0, 
%edx divl   (%ecx)

到b =无符号长,

idivl执行签名分割,将值存储为签名
divl执行无符号除法,将值存储为无符号

所以你是对的,操作确实溢出,输出是正确的,因为除法操作。

What is the difference of idivl and divl?

https://stackoverflow.com/a/12488534/1513286

答案 4 :(得分:0)

对于5/4,结果是未定义的行为。

但请注意,如果您将类型更改为无符号(对于常量只添加u后缀),不仅值适合,而且根据3.9.1 / 4,算术成为模运算和即使对于适合该类型的较大中间值,结果也是完美定义的。