编译以下代码时:
int f(int i1,int i2)
{
long l1=i1;
long l2=i2;
return l1*l2;
}
与
clang 10.1
的x86-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 == -2147483648
和i2 == -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/