Does compiler compile "Unreachable Codes" in MSIL or Native code in run time?
The question is a bit unclear but I'll take a shot at it.
First off, Adam's answer is correct insofar as there is a difference in the IL that the compiler emits based on whether the "optimize" switch is on or off. The compiler is much more aggressive about removing unreachable code with the optimize switch on.
There are two kinds of unreachable code that are relevant. First there is de jure unreachable code; that is, code that the C# language specification calls out as unreachable. Second, there is de facto unreachable code; that is code that the C# specification does not call out as unreachable, but nevertheless, cannot be reached. Of the latter kind of unreachable code, there is code that is known to the optimizer to be unreachable, and there is code not known to the optimizer to be unreachable.
The compiler typically always removes de jure unreachable code, but only removes de facto unreachable code if the optimizer is turned on.
Here's an example of each:
int x = 123;
int y = 0;
if (false) Console.WriteLine(1);
if (x * 0 != 0) Console.WriteLine(2);
if (x * y != 0) Console.WriteLine(3);
All three Console.WriteLines are unreachable. The first is de jure unreachable; the C# compiler states that this code must be treated as unreachable for the purposes of definite assignment checking.
The second two are de jure reachable but de facto unreachable. They must be checked for definite assignment errors, but the optimizer is permitted to remove them.
Of the two, the optimizer detects the (2) case but not the (3) case. The optimizer knows that an integer multiplied by zero is always zero, and that therefore the condition is always false, so it removes the entire statement.
In the (3) case the optimizer does not track the possible values assigned to y and determine that y is always zero at the point of the multiplication. Even though you and I know that the consequence is unreachable, the optimizer does not know that.
The bit about definite assignment checking goes like this: if you have an unreachable statement then all local variables are considered to be assigned in that statement, and all assignments are considered to not happen:
int z;
if (false) z = 123;
Console.WriteLine(z); // Error
if (false) Console.WriteLine(z); // Legal
The first usage is illegal because z has not been definitely assigned when it is used. The second usage is not illegal because the code isn't even reachable; z can't be used before it is assigned because control never gets there!
C# 2 had some bugs where it confused the two kinds of reachability. In C# 2 you could do this:
int x = 123;
int z;
if (x * 0 != 0) Console.WriteLine(z);
And the compiler would not complain, even though de jure the call to Console.WriteLine is reachable. I fixed that in C# 3.0 and we took the breaking change.
Note that we reserve the right to change up how the unreachable code detector and code generator work at any time; we might decide to always emit the unreachable code or never emit it or whatever.
The C# compiler can compile your application in either a debug configuration or a release configuration. This changes the behavior of whether or not unreachable code is compiled down to CIL and emitted into the output executable. Let's take a simple function for example:
public static int GetAnswer()
{
return 42;
Console.WriteLine("Never getting here!");
}
When you compile this in a debug configuration, the entire method (including the unreachable code) is emitted as CIL. It would look something like this (perhaps with some added nop
instructions to assist debugging):
.method public static int32 GetAnswer() cil managed
{
.maxstack 1
.locals init (int32)
ldc.i4.s 42 // load the constant 42 onto the stack
stloc.0 // pop that 42 from the stack and store it in a local
br.s L_000C // jump to the code at L_000C
ldstr "Never getting here!" // load the string on the stack
call void [mscorlib]System.Console::WriteLine(string) // call method
L_000C: ldloc.0 // push that 42 onto the stack from the local
ret // return, popping the 42 from the stack
}
The reason all this code is emitted is so the debugger can allow you to manually step into unreachable code, perhaps to force it to run under debugging circumstances.
That being said, when you build your project under a release configuration, the compiler will realize that, because the built assembly won't be stepped through in a debugger, it won't emit any unreachable code. The CIL would look like this:
.method public static int32 GetAnswer() cil managed
{
.maxstack 1
ldc.i4.s 42 // load the constant 42 onto the stack
ret // return, popping the 42 from the stack
}
Simple, clean, and optimized.
Console::WriteLine(string)
. I'm baffled... I do know that I've seen this behavior before, perhaps in previous versions of the compiler or the .NET Framework (hence why I could write out that CIL by hand) so it appears something may have changed, or there are forces at play that I'm unaware of. –
Peggi In release mode certain code blocks may get compiled out, but that's not what that error message means. So, to answer your question, code such as:
if (false)
// do something
would never make it into bytecode unless you have debugging enabled (that's because if you attach a debugger you could manually step into that if statement, so the code needs to be there).
When you receive the error message that debugging can't continue because of unreachable code, that tends to mean that the process you are debugging does not line up with the source code you are using (different versions, etc...).
static void Main() { if (1 == 0) { Console.WriteLine("Universe has exploded"); } }
, and the generated IL contains no call to Console.WriteLine
. –
Matias © 2022 - 2024 — McMap. All rights reserved.
if(1==1){//Reachable}else{//Unreachable code detected}
. Does the unreachable part compile? Does it allocate memory? – Dameif (i == 1)
where you, the programmer, know thati
will always be an even number? – Matias