<long>/<long> 与 <int>/<int> 的区别
Difference of <long>/<long> vs. <int>/<int>
编译以下代码时:
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 位除法。这可能是合理的,因为看起来 64 位 IDIV 可能比 32 位贵几倍,看一眼 Agner Fog 的指令表。尽管您可能希望在这种情况下它会使用 NEG 而不是除法(如果您想知道,是的,INT_MIN
的 NEG 是 INT_MIN
根据需要)。
(其实icc的-1
的特殊大小写就是帮助我实现反例的提示。)
乘法不需要这样的特殊处理,因为 imul
溢出时的行为已经是转换所需要的:它毫无例外地截断为 32 位。
编译以下代码时:
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 位除法。这可能是合理的,因为看起来 64 位 IDIV 可能比 32 位贵几倍,看一眼 Agner Fog 的指令表。尽管您可能希望在这种情况下它会使用 NEG 而不是除法(如果您想知道,是的,INT_MIN
的 NEG 是 INT_MIN
根据需要)。
(其实icc的-1
的特殊大小写就是帮助我实现反例的提示。)
乘法不需要这样的特殊处理,因为 imul
溢出时的行为已经是转换所需要的:它毫无例外地截断为 32 位。