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.

 

自己写过不少DelphiTurbo Pascal)代码了,可是这个问题还是让我出了身冷汗。程序的运行结果是i=6m=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,即0040080C0,完成循环;否则,eax减一(dec eax),然后指令向回跳转3个地址,回到004080BD这一行。整个过程完成了for循环,并且循环的次数早在进入循环时,便被确定下来,并且使用eax作为计数器,变量i在真正的汇编代码中都没有出现,此时我还发现源代码中m:=10这段代码也没有出现,难免奇怪,突然想到在编译时,程序给出了一个警告:

 

原来编译器见这行没有实际用处而将它优化掉了。看来这步优化确实有带来莫名错误的可能性,编译器给出警告是必须的。

   

复杂点的for循环

再看看未精简代码的汇编代码:

      

这次m的值并没有直接存放进eax,而是放入ebx中,变量c使用esi来存放,并且0值的赋予,是采用esi与自身异或的方法实现的。接下来的三行代码结构和先前所说的代码基本类似。mov ebx, $0000000a完成了m := 10的作用;inc函数则直接对应汇编中的inc指令,完成对变量c的自增一;最后将for循环的计数器eax减一,再进入下一次循环或者结束循环。很特别的是这段代码中多出了一个edi的使用(比如0040834c地址处的指令),开始我也不太明白,不过等我看到writeln函数对应的汇编代码时(下图),才想起edi用来计算for循环变量i的值。

 

writeln('i=',i,' m=',m,' c=',c)被拆成了三对,六行汇编代码来执行。第一行mov edx, $004083bc是将字符串’i=’放入edx中(即字符串’i=’在数据段的首地址是004083bc),然后调用writeOLString函数输出它(从变量名来看,这个函数应该是专门输出字符串的),接着将edi中的内容送入edx中(write*函数使用edx来存放输入参数),再调用writeOLong输出。mc的执行过程与此相同。

 

起始值非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循环的类型都已简单分析完毕。

posted @ 2004-12-08 01:14  monkeyking  阅读(1316)  评论(0)    收藏  举报