For-Schleife läuft rückwärts


Manchmal stellt man fest, dass die Laufvariable einer for-Schleife rückwärts gezählt wird. Also wenn die Schleife von 0 bis 5 läuft, dass die Zählvariable beim Debuggen aber in Wirklichkeit von 5 bis 0 zählt. Dies ist relativ einfach zu erklären. Ist es egal, wie rum die Schleife läuft, optimiert der Compiler den Code so, dass die Schleife rückwärts läuft. Aber warum ist das besser?

Nehmen wir mal ein Beispiel:

var
  ar                : array[0..5] of integer = (0, 1, 2, 3, 4, 5);

  i, r              : integer;
begin
  r := 0;
  for i := 0 to 5 do
  begin
    inc(r, ar[i]);
  end;
  Writeln(r);

Dieser Code macht eigentlich nichts sinnvolles. Das macht aber nichts, zur Verdeutlichung reicht es aus. Zu dem bleibt der generierte Code übersichtlich. Wichtig ist hier nur, dass der Compiler die Schleife optimieren kann. Gucken wir uns jetzt mal den generierten Code an:

Sieht für einen ungeübten im ersten Augenblick etwas kompliziert aus, ist es aber nicht. Mit ein wenig ASM Kenntnissen lässt sich leicht erschließen, was da passiert.

Die Zählvariable wird im Register EDX abgelegt:

mov edx, $00000006

Das interessante aber passiert in dem markiertren Block darunter:

dec edx
jnz -$08

An dieser Stelle wird die Zählvariable dekrementiert mit dec. dec hat die Eigenschaft, dass wenn das dekrementierte Register null wird, das Zero Flag (ZF) zusetzen. Dies wird ausgenutzt, um mittels des Sprungbefehls jnz ("springe, wenn das Zero Flag (ZF) im Flagsregister nicht gesetzt ist") zu entscheiden, ob gesprungen werden muss oder nicht. Ist das Zero Flag nicht gesetzt (null), wird gesprungen und zwar in diesem Fall 8 Byte zurück: -$08, was Adresse $00402561 ist, ausgehend von Adresse $00402569, und wie man sieht, ist das unser Schleifenrumpf. Ansonsten wird nicht gesprungen und im Code weitergemacht.

Und das, die Überprüfung eines Flags im Flagregister, ist einfacher und damit schneller als das Vergleichen zweier Register auf Gleichheit / Ungleichheit. Und das müsste man machen, wenn die Schleife vorwärts läuft. In diesem Fall muss man die Zählvariable immer mit dem Endwert vergleichen, was bedeuten würde, dass man er mit dem ASM-Befehl cmp zwei Register vergleicht und erst dann ein Flag im Flagregister auswerten kann.


2010-12-29T23:44:46 +0100, mail+homepage[at]michael-puff.de