只有在循环体不引用循环变量时才可能进行优化。在这种情况下,如果循环的下限为零,则编译器将反转循环。
如果循环体从不引用循环变量,那么编译器可以根据需要执行循环。所需要做的就是按照循环边界的要求多次执行循环体。事实上,编译器完全有理由优化循环变量。
考虑这个程序:
{$APPTYPE CONSOLE}
procedure Test1;
var
i: Integer;
begin
for i := 0 to 11 do
Writeln(0);
end;
procedure Test2;
var
i: Integer;
begin
for i := 0 to 11 do
Writeln(i);
end;
begin
Test1;
Test2;
end.
Test1 的主体由 XE7 编译成这段代码,32 位 Windows 编译器,带有发布选项:
Project1.dpr.9: for i := 0 to 11 do
00405249 BB0C000000 mov ebx,$0000000c
Project1.dpr.10:Writeln(0);
0040524E A114784000 移动 eax,[$00407814]
00405253 33D2 xor edx,edx
00405255 E8FAE4FFFF 调用@Write0Long
0040525A E8D5E7FFFF 调用@WriteLn
0040525F E800DBFFFF 调用@_IOTest
Project1.dpr.9: for i := 0 to 11 do
00405264 4B 十二月 ebx
00405265 75E7 jnz $0040524e
编译器正在向下运行循环,这可以通过使用dec 看出。请注意,循环终止测试是使用jnz 执行的,不需要cmp。那是因为dec 执行与零的隐式比较。
dec 的文档说明如下:
受影响的标志
CF 标志不受影响。 OF、SF、ZF、AF 和 PF 标志已设置
根据结果。
当且仅当dec 指令的结果为零时设置ZF 标志。而ZF 决定了jnz 是否分支。
Test2 发出的代码是:
Project1.dpr.17: for i := 0 to 11 do
0040526D 33DB xor ebx,ebx
Project1.dpr.18:Writeln(i);
0040526F A114784000 移动 eax,[$00407814]
00405274 8BD3 移动 edx,ebx
00405276 E8D9E4FFFF 调用@Write0Long
0040527B E8B4E7FFFF 调用@WriteLn
00405280 E8DFDAFFFF 调用@_IOTest
00405285 43 公司 ebx
Project1.dpr.17: for i := 0 to 11 do
00405286 83FB0C cmp ebx,$0c
00405289 75E4 jnz $0040526f
请注意,循环变量正在增加,我们现在有一个额外的 cmp 指令,在每次循环迭代时执行。
有趣的是,64 位 Windows 编译器不包含此优化。对于Test1,它会生成:
Project1.dpr.9: for i := 0 to 11 do
00000000004083A5 4833DB xor rbx,rbx
Project1.dpr.10:Writeln(0);
00000000004083A8 488B0D01220000 mov rcx,[rel $00002201]
00000000004083AF 4833D2 xor rdx,rdx
00000000004083B2 E839C3FFFF 调用@Write0Long
00000000004083B7 4889C1 mov rcx,rax
00000000004083BA E851C7FFFF 调用@WriteLn
00000000004083BF E86CB4FFFF 调用@_IOTest
00000000004083C4 83C301 添加 ebx,$01
Project1.dpr.9: for i := 0 to 11 do
00000000004083C7 83FB0C cmp ebx,$0c
00000000004083CA 75DC jnz 测试1 + 8 美元
我不确定为什么在 64 位编译器中没有实现这种优化。我的猜测是优化在现实世界中的影响可以忽略不计,设计人员选择不花精力为 64 位编译器实现它。