2010-04-09 6 views
8

Ich versuche zu verstehen, wie eine While-Schleife in IL aussieht. Ich habe diese C# Funktion geschrieben:While-Schleife in IL - warum Stloc.0 und Idloc.0?

static void Brackets() 
    { 
     while (memory[pointer] > 0) 
     { 
      // Snipped body of the while loop, as it's not important 
     } 
    } 

Die IL sieht wie folgt aus:

.method private hidebysig static void Brackets() cil managed 
{ 
    // Code size  37 (0x25) 
    .maxstack 2 
    .locals init ([0] bool CS$4$0000) 
    IL_0000: nop 
    IL_0001: br.s  IL_0012 
    IL_0003: nop 
    // Snipped body of the while loop, as it's not important 
    IL_0011: nop 
    IL_0012: ldsfld  uint8[] BFHelloWorldCSharp.Program::memory 
    IL_0017: ldsfld  int16 BFHelloWorldCSharp.Program::pointer 
    IL_001c: ldelem.u1 
    IL_001d: ldc.i4.0 
    IL_001e: cgt 
    IL_0020: stloc.0 
    IL_0021: ldloc.0 
    IL_0022: brtrue.s IL_0003 
    IL_0024: ret 
} // end of method Program::Brackets 

Zum größten Teil ist dies wirklich einfach, mit Ausnahme des Teils nach cgt.

Was ich nicht verstehe, ist die lokale [0] und die stloc.0/ldloc.0. Soweit ich es sehe, schiebt cgt das Ergebnis auf den Stack, stloc.0 holt das Ergebnis vom Stack in die lokale Variable, ldloc.0 schiebt das Ergebnis wieder auf den Stack und bratrue.s liest vom Stack.

Was ist der Zweck, dies zu tun? Könnte das nicht auf nur cgt gefolgt von brtrue.s verkürzt werden?

+1

Ein extra Nugget für Sie, basierend auf "wie es nicht wichtig ist" und Ihrem Twitter-Feed ... beachten Sie den Unterschied zwischen 'br * _s' und' br * '. Die '_s'-Varianten verwenden einen kleinen * relativen * Offset, und ** funktioniert nicht **, wenn die Positionen nicht nahe beieinander liegen (die Nicht-'_s'-Varianten verwenden einen * absoluten * Offset ohne die" kleine "Einschränkung). Wenn also Ihr Compiler die Größe des Körpers nicht voraussehen kann, verwenden Sie 'br *' gegenüber 'br * _s'. –

+0

@Marc Danke, genau in dieses Problem: D Meine Demo-App hat eine einzige Anweisung, so dass der Compiler _s Varianten erzeugte, aber mein echter Compiler hatte einen viel längeren Loop-Body. Positiver Nebeneffekt: Ich habe etwas aus dem "Illegalen Ein-Byte-Zweig" gelernt. Ausnahme: –

+0

IIRC, ILGenerator ist intelligent genug, um den richtigen Opcode für die vorliegende Situation auszugeben, wenn Sie die richtige Methode dafür verwenden. –

Antwort

6

Versuchen Sie, mit Optimierungen zu kompilieren.

+0

Das "repariert" es eigentlich - also ist es nur dort für Debug/Breakpoint vorgesehen? Nur neugierig, warum es überhaupt erzeugt wurde. –

+0

Ich würde vermuten, dass die beiden Anweisungen, die sich gegenseitig aufheben, von verschiedenen Teilen des Compilers stammen. Ich könnte mir definitiv vorstellen, dass alle While-Loops zunächst zu Nop konvertiert werden (als Sprungziel) .... ldloc. brtrue.s (Adr von Nop). Es scheint vernünftig genug zu sein, wenn Sie wissen, dass der Optimierer im nächsten Durchlauf den unnötigen Nop und so weiter entfernen wird. –

4

Das ist ein Debug Build (von nop). Alle Wetten ab, aber es sieht aus wie es ist einfach eine Bool Variable für Einfachheit Einführung:

goto testforexit; 
body: 
    .. 
testforexit: 
    bool tmp = memory[pointer] > 0; 
    if(tmp) goto body; 

Build-in-Release mit Optimierungen aktiviert, die solche Variablen entfernen sollte.