5

这个问题是另一个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

4

5 回答 5

3

是的,这是未定义的行为,如果unsigned long是 64 位类型,您得到的结果通常会有所不同。

¹ 它是 UB,因此无法保证。

于 2012-09-19T04:22:26.843 回答
2

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

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

于 2012-09-19T04:24:58.010 回答
2

中间结果

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

为什么当它们是变量时没有错误?

您明确告诉 gcc 根本不要优化 ( -O0),所以我的假设是它不知道那个时候iand的值j。通常你会因为不断的折叠而学习这些值,但就像我说的,你告诉它不要优化。

如果你重新运行它并且它仍然没有提到它,那么这个警告也有可能在优化器运行之前生成,所以它只是不够聪明,无法在这一步进行持续折叠。

于 2012-09-19T04:26:31.100 回答
1

您会收到两个警告,因为编译器知道操作数中的值。输出是正确的,因为两者都使用/b无符号长。要被除的临时值b必须保持更大或相等的数据类型范围,( i * j )或者( 100 * 30000000 )存储在与要被除的值具有相同数据类型范围的 CPU 寄存器中,如果b是,int则临时结果将是 a int,因为b是 ulong , int 不能除以 ulong,临时值存储到一个 ulong 中。

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

具有相同结构的程序,仅更改bint.s 代码将只有两行。

cltd 
idivl   (%ecx) 

到 b = int

movl    $0, 
%edx divl   (%ecx)

到 b = 无符号长,

idivl 执行有符号除法,将值存储为有符号
divl 执行无符号除法,将值存储为无符号

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

idivl 和 divl 有什么区别?

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

于 2012-09-19T05:13:58.420 回答
0

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

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

于 2012-09-19T05:38:50.470 回答