Dear friend, in this article I will take you from the backdoor of C# code execution and show you the step-by-step operation of code execution in C#. You might think that "I do not bother at all with an execution plan or I don't want to poke my nose into it". Yes, you are right to a certain point, because code execution may not help you to increase your CTC. (Yes I am talking about salary , the first thing that comes to mind after getting an interview call... Ha Ha.) It will however make you a better programmer from a good programmer or increase your peace of mind when you see the same output produced by the computer after executing the same program in your mind.
And us developer people generally ignore it because the .NET framework is trained enough to execute code properly to get the desired output.
Here we will start our journey by observing a very small example and gradually we will proceed to complex ones and in our journey we will try to concentrate on a few things that we have studied in our college days but not seen practically.
Let me ask you one question. Do you still remember assembly language programming? If you are not a chip level programmer then it's a blur in your mind and most probably you are trying to recap a few instructions of the 8085 or 8086 microprocessor from your good old college days. I am talking about low-level programming because the IL code produced by the .NET Framework is very similar to assembly level programming. And if you have a good idea of that then this tutorial will be a piece of cake for you.
Understanding a few instructions at first
Let's learn a few IL code instructions produced by .NET. Before beginnig that, a quick review is nevcessary for a better understanding. Actually the number of instruction sets depends on the computer architecture. If you use an 8-bit computer (what the hell, man; people are using 64-bit and still now you are in 8 bits!! Ha Ha) then the total number of instructions will be 256. For your 64-bit system calculation remains for you . Obviously our interest will be those instructions that we need to understand to read this article completely.
- ldstr : load string constant onto stack
- ldc.i4.s : load 32 bit numeric constant onto stack
- ldlco : load local variable data onto stack (top of the stack obviously)
- call : call to static function
With those instructions let's clear up a few basic theoretical concepts, as in the following:
- All data (value types) are first created on the stack and then moved to a local variable.
- For arithmetic or any data manipulation purposes a stack is used.
- Before calling a function the function argument / parameter loads one stack and then during execution it is popped from the stack.
- The return value from the function does not get to the caller function (what we generally think) it is just put onto the stack
That is however too much theory. Let's see it in action.
How does it get the result in your day 1 program?
Yes, I am talking about the "Hello World" program. Let's print one hello world message in a program and see the execution steps. In the following my simple name print program is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
namespace Test1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Sourav Kayal");
}
}
And here is the IL code:
Start to discuss the step-by-step of code execution.
IL_0000 : ldstr "sourav Kayal" : Load string constant onto stack
IL_0005: call: Call to WriteLine() function with value of top of stack
IL_000a: ret: Return from the function.
Here we are seeing the printing of a simple string in the console output window. At first the string is loaded into the stack (because this is the argument of the WriteLine function) then the WriteLine function is called and the argument value is taken from the top of the stack.
Note: The point is clear here; before calling a function it is necessary for the argument to be loaded onto the stack. If you don't believe that then wait for the next few examples.
The following prints a string by declaring it first.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
namespace Test1
{
class Program
{
static void Main(string[] args)
{
string name = "Sourav Kayal";
Console.WriteLine(name);
}
}
}
This is the corresponding IL code.
IL_0000: ldstr : Load String constant onto stack
IL_0005:stloc.0 : Store data (here a string) in local-0 variable from top of stack.
IL_0006:ldloc.0 : Load data onto the stack from local variable-0
IL_0007:call : Call to WriteLine() static function.
IL_000c:ret : Return from the program.
In this program we are also seeing at first the string constant is created on the stack and then it's loaded to a local variable. And when we are using this local variable in our WriteLine() function as a argument at first the value of local variable is being loaded onto the stack and then it's passed as an argument. Here we can clearly see again "Before calling a function it is necessary to push the argument onto the stack".
Sourav, stop working with Strings and show something else.
Then let me show an example with an integer value. Here is my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
namespace Test1
{
class Program
{
static void Main(string[] args)
{
int a = 100;
Console.WriteLine(a);
}
}
}
In this program we are creating one integer variable and initializing it with the value 100 and passing it as an argument of the WriteLine() function. The following is the IL code window. Here we will see a very similar output with the String allocation process not much different.
IL_0000 : ldc.i4.s : Load numeric constant onto stack
IL_0002 : stloc.0 : Store value to local variable from top of the stack.
IL_0003 :ldloc.0 : Load local variable data onto stack. (See, again the value is being loaded onto the stack.)
IL_0004 :call : Call to the static function with an argument from the top of the stack.
Let's see how the addition operation is performed on the stack.
Here is the very simple program for the addition operation. Let's see the line-by-line execution.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
namespace Test1
{
class Program
{
static void Main(string[] args)
{
int a = 100;
int b = 100;
Console.WriteLine(a+b);
}
}
}
The output IL code window is here.
IL_0000: ldc.i4.s : Load integer constant onto stack (first variable).
IL_0002:stloc.0 : Move the top of the stack to a local variable. (0 indicating first variable.)
IL_0003:ldc.i4.s : Load an integer constant onto the stack (second variable).
IL_0005:stloc.1 : Move top of stack to local variable(1 indicating second variable).
IL_0006:ldloc.0 : Load local variable 0 (first variable) on stack.
IL_0007.ldloc.1 : Load local variable 1 (second variable) on stack.
IL_0008:add : Perform add operation. (See, the Add operation is performed with the stack value, top two values.)
Note: Before any arithmetic operation the necessary values need to be loaded onto the stack. You may try with subtraction or division.
We return value the happily, but where is the value returned?
Why? The value is returned to the caller function?
Very frequently we write a return statement to return something from a function and when we began learning programming our teacher taught us that when we return something from a function it is returned to the caller function. (My teacher also told me that.) But here we will see what happens exactly, when we return some value from a function.
Here is my simple code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
namespace Test1
{
class Program
{
static Int32 Call()
{
return 100;
}
static void Main(string[] args)
{
Call();
}
}
}
In the right side my main function IL code is shown and the left window contains the body of the Call function. This call function returns the value (100) to the Main function but if we closely look at the IL code of the Call() function then we will see the value is created on the top of the stack (ldc.i4.s) then the control is being returned from the Call function and along with the return statement is the attached value.
Note: the Return statement does not return anything, just keeps the latest value on the stack.
Conclusion
Here I have tried to represent a few first days of programming concepts in my own style. If we really like it (or even dislike it) then please leave your valuable comment in the following. Don't forget "Your single comment is enough to reduce my sleep time in half due to preparation of a more interesting document in part 2".