Delphi的For语句汇编代码分析
你能说出下面这段Delphi代码的输出结果么?
program TestFor;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
i,m,c: integer;
begin
m := 5;
c := 0;
for i:=1 to m do
begin
m := 10;
Inc(c);
end;
writeln('i=',i,' m=',m,' c=',c);
readln;
end.
自己写过不少Delphi(Turbo Pascal)代码了,可是这个问题还是让我出了身冷汗。程序的运行结果是i=6,m=10, c=5;而非我最初想的循环10次。我不禁去探究一下Delphi编译后的代码。
精简的for循环
简单起见,我把上面的代码再度缩减,只留下最关键的内容:
program TestFor;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
i,m: integer;
begin
m := 5;
for i:=1 to m do
begin
m := 10;
end;
readln;
end.
在“readln;”这一行设置断点,运行程序,程序会自动停在断点处;从IDE窗口中选择“View | Debug Window | CPU”便能看到此段代码相应的汇编代码,如下图:
程序直接将5放入eax寄存器中(mov eax, $00000005),接着测试eax(我估计是测试eax是否为零),如果小于或者等于则指令地址加3,即
原来编译器见这行没有实际用处而将它优化掉了。看来这步优化确实有带来莫名错误的可能性,编译器给出警告是必须的。
复杂点的for循环
再看看未精简代码的汇编代码:
这次m的值并没有直接存放进eax,而是放入ebx中,变量c使用esi来存放,并且0值的赋予,是采用esi与自身异或的方法实现的。接下来的三行代码结构和先前所说的代码基本类似。mov ebx, $
writeln('i=',i,' m=',m,' c=',c)被拆成了三对,六行汇编代码来执行。第一行mov edx, $004083bc是将字符串’i=’放入edx中(即字符串’i=’在数据段的首地址是004083bc),然后调用writeOLString函数输出它(从变量名来看,这个函数应该是专门输出字符串的),接着将edi中的内容送入edx中(write*函数使用edx来存放输入参数),再调用writeOLong输出。m和c的执行过程与此相同。
起始值非1时的for循环
将上述代码的for语句更改为
for i:=3 to m do
再次编译的汇编代码如下
这里有两行出现关键的变化。增加了sub eax,$03,这行用m-3计算循环需要的次数;jl +$10是从jle变化来的,如果m-3小于零则立即跳过循环。当起始值为负数时此方法同样可行。
使用非整型计数器的For循环
测试代码如下:
program TestFor;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
MyEnum = (Enum1, Enum2, Enum3, Enum4);
var
i,m: MyEnum;
a: integer = 0;
begin
for i:=low(m) to high(m) do
begin
inc(a);
end;
readln;
end.
编译后的汇编代码如下:
编译器直接计算出需要的需要的循环次数。只需要循环4次,所以只使用eax中的al部分,其余部分没有太多变化。
基本上for循环的类型都已简单分析完毕。

浙公网安备 33010602011771号