<long>/<long> 与 <int>/<int> 的区别

FPK*_*FPK 5 c compiler-construction optimization clang compiler-optimization

编译以下代码时:

int f(int i1,int i2)
{
    long l1=i1;
    long l2=i2;
    return l1*l2;
}
Run Code Online (Sandbox Code Playgroud)

clang 10.1forx86-64和 with -O3,我得到

    mov     eax, edi
    imul    eax, esi
    ret
Run Code Online (Sandbox Code Playgroud)

编译器认识到不需要完整的 64 位操作。

但是,当我用除法替换乘法时:

int f(int i1,int i2)
{
    long l1=i1;
    long l2=i2;
    return l1/l2;
}
Run Code Online (Sandbox Code Playgroud)

它编译成

    movsx   rax, edi
    movsx   rsi, esi
    cqo
    idiv    rsi
    ret
Run Code Online (Sandbox Code Playgroud)

所以它使用 64 位除法(与 gcc 相同)。

阻止在这里使用 32 位除法的反例是什么?

Nat*_*dge 6

考虑当i1 == INT_MIN == -2147483648和时会发生什么i2 == -1

为了比较,让我们也考虑一下

int g(int i1, int i2) {
    return i1/i2;
}
Run Code Online (Sandbox Code Playgroud)

它编译为一个简单的 32 位idiv.

如果调用g(INT_MIN, -1),除法将溢出,因为结果2147483648不适合int。这会导致 C 级别的未定义行为,实际上该idiv指令将生成异常。

如果您改为调用f(INT_MIN, -1),则除法不会溢出,因为结果确实适合 a long。现在该return语句导致它被int通常的整数转换转换为。由于该值不适合有符号类型int,因此结果是实现定义的,并且 gcc记录了它将执行的操作:

当值无法在该类型的对象中表示时,将整数转换为有符号整数类型的结果或产生的信号(C90 6.2.1.2、C99 和 C11 6.3.1.3)。

转换为宽度为 N 的类型,该值以 2^N 为模减少到类型的范围内;没有发出信号。

所以需要生成代码,保证不产生异常,返回值是-2147483648(相当于2147483648mod 2^32)。32 位除法不会这样做,但 64 位除法会。

有趣的是,icc通过特殊情况处理这个问题i2 == -1仅在这种情况下进行 64 位除法,否则进行 32 位除法。这可能是合理的,因为看起来 64 位 IDIV 可能比 32 位贵几倍,看看 Agner Fog 的指令表。尽管您可能希望在这种情况下使用 NEG 而不是除法(如果您想知道,是的,NEG ofINT_MIN是您INT_MIN想要的)。

(其实,-1by icc的特殊大小写是帮助我实现反例的提示。)

乘法不需要这种特殊处理,因为imul溢出时的行为已经是转换所需要的:它毫无例外地截断为 32 位。