使用gcc 7.2,我发现当循环迭代999次时,编译器的输出会发生变化。
特别是此程序(link to compiler explorer using gcc 7.2):
int f()
{
int i=0x7fffffff-998;
while (i+1>i)
i++;
return i;
}
编译(使用-O3 -fwrapv):
f():
mov eax, 2147483647
ret
但是,如果我将998更改为999,它将编译为:
f():
xor eax, eax
movdqa xmm0, XMMWORD PTR .LC0[rip]
movdqa xmm2, XMMWORD PTR .LC1[rip]
jmp .L2
.L3:
movdqa xmm0, xmm1
.L2:
movdqa xmm1, xmm0
add eax, 1
cmp eax, 250
paddd xmm1, xmm2
jne .L3
pshufd xmm0, xmm0, 255
movd eax, xmm0
ret
.LC0:
.long 2147482648
.long 2147482649
.long 2147482650
.long 2147482651
.LC1:
.long 4
.long 4
.long 4
.long 4
为什么输出会改变,并且有一个开关来控制行为改变的阈值?
由于带符号的溢出是不确定的行为,因此默认情况下,编译器会将此程序转换为无限循环。这就是为什么在编译过程中需要-fwrapv选项的原因。
答案 0 :(得分:2)
这基本上是GCC源中任意常数的结果。
GCC具有一个内部参数,该参数控制在优化过程中暂定展开循环的次数:
/* The maximum number of iterations of a loop the brute force algorithm
for analysis of # of iterations of the loop tries to evaluate. */
DEFPARAM(PARAM_MAX_ITERATIONS_TO_TRACK,
"max-iterations-to-track",
"Bound on the number of iterations the brute force #"
" of iterations analysis algorithm evaluates.",
1000, 0, 0)
如果GCC没有特殊的逻辑来执行某种代数变换以获取迭代计数,则用于分析循环。
如果将此参数更改为其他值,则从结果到另一个的切换将以其他魔术值进行。使用您的原始998的价值,我得到了:
$ gcc -O3 -fwrapv -S -o- --param max-iterations-to-track=997 t.c | grep jl
jl .L3
$ gcc -O3 -fwrapv -S -o- --param max-iterations-to-track=998 t.c | grep jl
jl .L3
$ gcc -O3 -fwrapv -S -o- --param max-iterations-to-track=999 t.c | grep jl
$ gcc -O3 -fwrapv -S -o- --param max-iterations-to-track=1000 t.c | grep jl
这些参数是内部实现的详细信息,可以随时更改含义,也可以完全消失。
(我使用的基于GCC 6.3的编译器版本在未优化的情况下不使用这些向量指令,而是有条件jl
跳转的序列,并且临界点略有不同,大概是由于其他优化。)