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

标签 c optimization compiler-construction clang compiler-optimization

编译以下代码时:

int f(int i1,int i2)
{
    long l1=i1;
    long l2=i2;
    return l1*l2;
}

clang 10.1x86-64-O3一起,我得到
    mov     eax, edi
    imul    eax, esi
    ret

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

但是,当我用除法代替乘法时:
int f(int i1,int i2)
{
    long l1=i1;
    long l2=i2;
    return l1/l2;
}

它编译成
    movsx   rax, edi
    movsx   rsi, esi
    cqo
    idiv    rsi
    ret

因此,它使用64位除法(与gcc相同)。

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

最佳答案

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

为了进行比较,我们还考虑

int g(int i1, int i2) {
    return i1/i2;
}

编译为简单的32位idiv

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

如果改为调用f(INT_MIN, -1),则除法不会溢出,因为结果确实适合long。现在,return语句使它通过通常的整数转换转换为int。由于该值不适合带符号的int类型,因此结果是实现定义的,gcc documents将执行以下操作:

The result of, or the signal raised by, converting an integer to a signed integer type when the value cannot be represented in an object of that type (C90 6.2.1.2, C99 and C11 6.3.1.3).

For conversion to a type of width N, the value is reduced modulo 2^N to be within range of the type; no signal is raised.



因此,它需要生成代码以确保不生成异常,并且返回的值是-2147483648(等效于2147483648 mod 2 ^ 32)。 32位除法不会执行此操作,但64位除法会执行此操作。

有趣的是,icc handles this通过特殊设置i2 == -1的大小写并仅在这种情况下进行64位除法,否则进行32位除法。看一下Agner Fog的指令表,看似64位IDIV的价格可能是32位的几倍,所以这可能是合理的。尽管您可能希望在这种情况下使用NEG而不是进行除法(并且,如果您想知道,是的,INT_MIN的NEG是所需的INT_MIN)。

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

乘法不需要这种特殊的处理,因为imul在溢出时的行为已经是转换所需要的:它被截断为32位,无一异常(exception)。

关于c - <long>/<long>与<int>/<int>的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61856447/

相关文章:

scala - Scala 编译器会提升正则表达式吗

mysql 查询在数据库上消耗很长时间,但在其他数据库上运行良好

compiler-construction - 为 ARM 构建交叉编译器工具链时出现错误(错误 2)

compiler-construction - 如何使用 ANTLR 修改 CommonTokenStream 中的 token 文本?

c - 如何通知 select() 立即返回?

c - 编译 glibc 时 mipsel-gcc 产生的奇怪加载指令

c# - 可空的 lambda 参数是否应该替换为透明的实现?

c - 子剪辑 : add revision no in file

c - 将 "adress"作为函数参数传递 - Python 3

复制链表中的结构