If anyone has ever touched any assembler then you will be aware of opcodes that do comparisons, this includes assembly languages like MIPS.
I present first the C# code of the for loop.
using System; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { for (int i = 0; i < 10; i++) { Console.WriteLine(i); } } } }
Now here is my implementation of the same using MSIL.
.assembly extern mscorlib {} .assembly lesthan { .ver 1:0:0:0 } .module LessThan.exe .method static void main() { .entrypoint .maxstack 2 .locals init (int32, int32) ldc.i4 10 stloc.0 ldc.i4 0 stloc.1 Expr: ldloc.1 ldloc.0 blt Body ret Body: ldloc.1 call void [mscorlib]System.Console::WriteLine(int32) ldc.i4 1 ldloc.1 add stloc.1 br Expr }
This code is pretty easy to disassemble. I create a branch block that defines the expression (.Expr) i (where i is the current value in mem location 1) < 10 (location 0) if this expression is true then we branch to .Body which basically prints out the current value of i as well as incrementing that value by 1, we then check the expression holds true again - if so we go again, if not we return power to the caller.
There is a key difference between what the C# compiler will give you and what I have given you, the first is that I have used fairly meaningful branch names, and the second is that I have decided to initialize two local variables holding both the counter (initially 0) and the max (10) - the C# compiler in the above code will only create a local variable for i, 10 will be loaded onto the stack when required.
The C# compiler will honour the fact that 10 is not assigned to any variable so the below is a more accurate representation of what the compiler will generate us, i is a local variable where as the integer 10 is loaded onto the stack when the expression is evaluated.
.assembly extern mscorlib {} .assembly lesthanC { .ver 1:0:0:0 } .module LessThanC.exe .method static void main() { .entrypoint .maxstack 2 .locals init (int32) ldc.i4 0 stloc.0 Expr: ldloc.0 ldc.i4 10 blt Body ret Body: ldloc.0 call void [mscorlib]System.Console::WriteLine(int32) ldc.i4 1 ldloc.0 add stloc.0 br Expr }
Here is in fact what the C# compiler generates for us (release mode, code optimization).
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 20 (0x14) .maxstack 2 .locals init ([0] int32 i) IL_0000: ldc.i4.0 IL_0001: stloc.0 IL_0002: br.s IL_000e IL_0004: ldloc.0 IL_0005: call void [mscorlib]System.Console::WriteLine(int32) IL_000a: ldloc.0 IL_000b: ldc.i4.1 IL_000c: add IL_000d: stloc.0 IL_000e: ldloc.0 IL_000f: ldc.i4.s 10 IL_0011: blt.s IL_0004 IL_0013: ret } // end of method Program::Main
Have a look at the MSIL. We first store the int 0 onto the stack, then we branch to IL_000e - here we check that i < 10 if so we branch to IL_0004 where we load the value of i onto the stack then print it out to the console window, we then push i onto the stack, and then push 1 onto the stack then add the two popping them both off the stack and store the result of the addition we then perform the i < 10 expression again until it evaluates to false then we return power to the caller.
Comments