在IL中,標號(label)是一個末尾帶有冒號(即:)的名稱。它使咱們可以從代碼的一部分無條件地跳轉到另外一部分。咱們常常在由反編譯器生成的IL代碼中看到這個標號。例如:程序員
IL_0000: ldstr "hi"編程
IL_0005: call void [mscorlib]System.Console::WriteLine(class System.String)編程語言
IL_000a: call void zzz::abc()ide
IL_000f: ret函數
在冒號前面的詞就是標號。在下面給出的程序中,咱們在函數abc中建立一個名爲a2的標號。指令br用於隨時跳轉到程序中的任何標號。oop
a.ilspa
.assembly mukhi {}設計
.class private auto ansi zzz extends System.Objectci
{作用域
.method public hidebysig static void vijay() il managed
{
.entrypoint
.locals (int32 V_0,class zzz V_1)
newobj instance void zzz::.ctor()
stloc.1
call int32 zzz::abc()
stloc.0
ldloc.0
call void [mscorlib]System.Console::WriteLine(int32)
ret
}
.method private hidebysig static int32 abc() il managed
{
.locals (int32 V_0)
ldc.i4.s 20
br.s a2
ldc.i4.s 30
a2: ret
}
}
Output
20
函數abc示範了這個概念。在這個函數中,代碼繞過了指令ldc.i4.s 30。所以,返回值顯示爲20而不是30。從而,IL使用br指令來無條件地跳躍到代碼的任何部分。(程序集指令br獲取4字節,而在.sr以前的br,即br.s獲取1字節,對於每一個標記爲.s的指令,解釋都是相同的。)
br指令是IL得以運轉的關鍵組件之一。
a.cs
class zzz
{
static bool i = true;
public static void Main()
{
if (i)
System.Console.WriteLine("hi");
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld bool zzz::i
brfalse.s IL_0011
ldstr "hi"
call void [mscorlib]System.Console::WriteLine(class System.String)
IL_0011: ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.1
stsfld bool zzz::i
ret
}
}
Output
hi
在咱們的C#程序中,咱們將靜態變量初始化爲true值。
l 靜態變量,若是它們是字段,就會在靜態構造函數.cctor中被初始化。這會在上面的程序中顯示。
l 另外一方面,局部變量在它們所在的函數中被初始化。
這裏,讓人吃驚的是,使用ldc指令將值1放置在棧上的靜態構造函數中。即便同時在C#和IL中定義了字段i,仍是沒有true或false這樣的符號。
接下來,使用stsfld將靜態變量i初始化爲值1,儘管變量是布爾類型的。這就證明了IL支持bool數據類型,它不會識別出單詞true或false。所以,在IL中,布爾值分別只是數字1或0的別名。
布爾運算符TRUE或FALSE是由C#引進的關鍵字,用來使程序員的工做更加輕鬆。因爲IL不直接支持這些關鍵字,因此它會替代地使用數字1或0。
指令ldsfld把靜態變量的值加載到棧上。指令brfalse對棧進行掃描。若是它找到了數字1,它就會將其解釋爲TRUE,而若是它找到了數字0,它就會將其解釋爲FALSE。
在這個例子中,它在棧上找到的值是1或TRUE,因此它不會跳轉到標號IL_0011。在從C#到IL的轉換中,ildasm使用以IL_開始的名稱來代替標號。
指令brfalse表示「若是FALSE就跳轉到標號」。這不一樣於br,後者老是會致使一個跳轉。從而,brfalse是一個有條件的跳轉指令。
在IL中沒有提供if語句功能的指令。C#中的if語句會被轉換爲IL中的轉移(branch)指令。咱們所處的任何彙編器,都沒有像if結構體這樣的高級概念。
能夠看到,咱們剛剛學到的那些知識,對於咱們掌握IL是很是重要的。這將幫助咱們得到——區別關於哪一個概念是IL的一部分而哪些是由編程語言的設計者引進——的能力
尤爲須要注意的是,若是IL不支持某個特性,它就不能用任何.NET編程語言實現。從而,熟悉IL所支持的各類概念的重要性——怎麼強調都不過度。
a.cs
class zzz
{
static bool i = true;
public static void Main()
{
if (i)
System.Console.WriteLine("hi");
else
System.Console.WriteLine("false");
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld bool zzz::i
brfalse.s IL_0013
ldstr "hi"
call void [mscorlib]System.Console::WriteLine(class System.String)
br.s IL_001d
IL_0013: ldstr "false"
call void [mscorlib]System.Console::WriteLine(class System.String)
IL_001d: ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.1
stsfld bool zzz::i
ret
}
}
Output
hi
在編程語言中,if-else語句是極其容易理解的,可是在IL中它倒是至關使人困惑的。IL檢查棧上的值是1仍是0。
l 若是棧上的值是1,正如這個例子中的那樣,它調用帶有參數hi的WriteLine函數,並隨後使用無條件跳轉指令br,跳轉到標號IL_001d。
l 若是棧上的值是0,代碼跳轉到IL_0013,而且WriteLine函數會打印出false。
從而,爲了在IL中實現if-else結構,須要一個有條件跳轉和一個無條件跳轉。若是咱們使用多個if-else語句,那麼IL代碼的複雜度就會動態增長。
如今,能夠看出編譯器的編寫者的智商了。
a.cs
class zzz
{
public static void Main()
{
}
void abc( bool a)
{
if (a)
{
int i = 0;
}
if ( a)
{
int i = 3;
}
}
}
a.il
.assembly mukhi {}
.class public auto ansi zzz extends [mscorlib]System.Object
{
.field private int32 x
.method public hidebysig static void vijay() il managed
{
.entrypoint
ret
}
.method private hidebysig instance void abc(bool a) il managed
{
.locals (int32 V_0,int32 V_1)
ldarg.1
brfalse.s IL_0005
ldc.i4.0
stloc.0
IL_0005: ldarg.1
brfalse.s IL_000a
ldc.i4.3
stloc.1
IL_000a: ret
}
}
C#編程語言就更復雜了。在內部的一組括號中,咱們不能建立以前已經在外部建立的變量。上面的C#程序在語法上是正確的,由於括號都是在同一級別上。
在IL中,會稍微簡單一些。這兩個i會變成兩個單獨的變量V_0和V_1。所以,IL不會暴露施加在變量上的任何約束。
a.cs
class zzz
{
static bool i = true;
public static void Main()
{
while (i)
{
System.Console.WriteLine("hi");
}
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.method public hidebysig static void vijay() il managed
{
.entrypoint
br.s IL_000c
IL_0002: ldstr "hi"
call void [mscorlib]System.Console::WriteLine(class System.String)
IL_000c: ldsfld bool zzz::i
brtrue.s IL_0002
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.1
stsfld bool zzz::i
ret
}
}
當看到反彙編的代碼時,你將理解爲何程序員不以編寫IL代碼來謀生。即便一個簡單的while循環,在轉換爲IL後都會變得驚人的複雜。
對於一個while結構,會建立一個到標號IL_000c的無條件跳轉,它位於函數的結尾。這裏,它加載靜態變量i的值到棧上。
下一個指令brtrue,作的事情和指令brfalse所作的正好相反。實現以下:
l 若是棧頂的值——例如,字段i的值——是1,那麼它會跳轉到標號IL_0002。而後值hi被放到棧上而且WriteLine函數會被調用。
l 若是棧頂的值是0,那麼程序將跳轉到ret指令。
上面的程序,正如你所看到的那樣,並不打算中止。它會繼續流動,就像一個起源於一個巨大冰川的水流。
a.cs
class zzz
{
static int i = 2;
public static void Main()
{
i = i + 3;
System.Console.WriteLine(i);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static int32 i
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld int32 zzz::i
ldc.i4.3
add
stsfld int32 zzz::i
ldsfld int32 zzz::i
call void [mscorlib]System.Console::WriteLine(int32)
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.2
stsfld bool zzz::i
ret
}
}
Output
5
IL沒有操做符用來作兩個數字的加法,而是使用add指令。
add指令須要用來作加法的兩個數字,也就是棧上開始的2個有效的元素。所以,ldsfld指令把靜態變量i的值和常量值3放到棧上。隨後,add指令把它們相加並把結果放到棧上。它還會從棧上移除用來作加法的2個數字。
一旦指令被執行了,IL中的大多數指令就會擺脫棧上的參數,也就是該指令要操做的參數。
使用指令stsfld將靜態變量i初始化爲加法的結果總和。剩下的代碼直接顯示了變量i的值。
在IL中沒有++操做符的等價物。它會被轉換爲指令ldc.i4.1。一樣,兩個數字相乘,須要使用mul指令;相減,就使用sub指令,等等。它們在IL中都有等價物。以後的代碼保持不變。
a.cs
class zzz
{
static bool i;
static int j = 19;
public static void Main()
{
i = j > 16;
System.Console.WriteLine(i);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.field private static int32 j
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld int32 zzz::j
ldc.i4.s 16
cgt
stsfld bool zzz::i
ldsfld bool zzz::i
call void [mscorlib]System.Console::WriteLine(bool)
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.s 19
stsfld int32 zzz::j
ret
}
}
Output
True
如今咱們將研究IL如何處理條件操做符。讓咱們考慮C#中的語句j>i16。IL首先把值j放到棧上,也就是常量值16的前面。隨後它會調用cgt操做,這是它首次在咱們的源代碼中出現。這個指令檢查棧上的第1個值是否大於第2個。若是是,那麼它會把值1(TRUE)放到棧上,不然它會把值0(FALSE)放到棧上。這個值隨後被存儲到變量i中。使用WritleLine函數,就會生成布爾值的輸出,從而咱們看到顯示True。
一樣,操做符<被轉換爲clt指令,它會檢查棧上的第1個值是否小於第2個。從而咱們看到IL具備它本身的一套邏輯操做符,對基本的邏輯運算進行內部處理。
a.cs
class zzz
{
static bool i;
static int j = 19;
public static void Main()
{
i = j == 16;
System.Console.WriteLine(i);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.field private static int32 j
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld int32 zzz::j
ldc.i4.s 16
ceq
stsfld bool zzz::i
ldsfld bool zzz::i
call void [mscorlib]System.Console::WriteLine(bool)
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.s 19
stsfld int32 zzz::j
ret
}
}
Output
False
操做符==就是EQUALITY操做符。它也須要棧上的2個操做數(operand)來檢查相等性。此後它使用ceq指令來檢查相等性。若是相等,它會把值1(TRUE)放到棧上,若是不相等,它會把值0(FALSE)放到棧上。指令ceq是IL的邏輯指令集的不可缺乏的一部分。
a.cs
class zzz
{
static bool i;
static int j = 19;
public static void Main()
{
i = j >= 16;
System.Console.WriteLine(i);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.field private static int32 j
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld int32 zzz::j
ldc.i4.s 16
cgt
ldc.i4.0
ceq
stsfld bool zzz::i
ldsfld bool zzz::i
call void [mscorlib]System.Console::WriteLine(bool)
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.s 19
stsfld int32 zzz::j
ret
}
}
Output
False
「小於等於」(<=)和「大於等於」(>=)的實現有一點複雜。實際上它們都具備2個融合在一塊兒的條件。
在>=的狀況中,IL首先使用cgt指令來檢查第1個數字是否大於第2個數字。若是是,它將返回值1,不然就返回值0。若是第1個條件是FALSE,那麼ceq指令將會檢查這2個數字是否相等。若是相等,它就返回TRUE,不然就返回FALSE。
讓咱們從一個稍微不一樣的角度來分析上面的IL代碼。咱們對這兩個值19和16進行比較。在這個例子中,由於19大於16,因此指令cgt將會把值1放到棧上。
讓咱們在靜態構造函數中將字段j的值修改成1。如今,因爲數字1不大於16,因此cgt指令將把值FALSE或0放到棧上。此後,使用ldc指令把另外一個0放到棧上。如今當ceq指令比較兩個值時,由於它們都是0,因此它會返回TRUE。
如今,若是咱們將j修改成16,cgt指令將返回FALSE,由於16不大於16。此後,因爲使用ldc指令把值0放到棧上,這兩個傳遞到ceq的值將都是0。因爲0與0是相等的,因此返回值是1或TRUE。
若是你不能理解上面的解釋,那麼就從源代碼中移除ldc.i4.0和ceq這兩行,並觀察輸出。
a.cs
class zzz
{
static bool i;
static int j = 19;
public static void Main()
{
i = j != 16;
System.Console.WriteLine(i);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static bool i
.field private static int32 j
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldsfld int32 zzz::j
ldc.i4.s 16
ceq
ldc.i4.0
ceq
stsfld bool zzz::i
ldsfld bool zzz::i
call void [mscorlib]System.Console::WriteLine(bool)
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.s 19
stsfld int32 zzz::j
ret
}
}
Output
True
「不等於」操做符,也就是!=,是==的相反操做。它使用了兩個ceq指令。第1個ceq指令用來檢查棧上的值是否相等。若是它們是相等的,它就會返回TRUE;不然就返回FALSE。
第2個ceq將前面的ceq結果和FLASE進行比較。若是第1個ceq的結果是TRUE,那麼最後的答案就是FALSE,反之亦然。
這確實是一種首創的方式來對一個值求否。
a.cs
class zzz
{
static int i = 1;
public static void Main()
{
while ( i <= 2)
{
System.Console.WriteLine(i);
i++;
}
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static int32 i
.method public hidebysig static void vijay() il managed
{
.entrypoint
br.s IL_0018
IL_0002: ldsfld int32 zzz::i
call void [mscorlib]System.Console::WriteLine(int32)
ldsfld int32 zzz::i
ldc.i4.1
add
stsfld int32 zzz::i
IL_0018: ldsfld int32 zzz::i
ldc.i4.2
ble.s IL_0002
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.s 1
stsfld int32 zzz::i
ret
}
}
Output
1
2
在介紹完條件語句以後,咱們如今將關注於while循環。這種轉換是必須的,由於咱們在諸如while這樣的循環中使用條件語句。包括條件的while循環稍微有點複雜。
讓咱們直接到標號IL_0018上,它位於IL代碼中zzz函數的結尾。這裏存在着一個條件。i的值(即1)被存儲到棧上。接下來,常量2被放到棧上。
若是你再次訪問C#代碼,那麼while語句中的條件就是i <= 2。指令ble.s是基於兩個構造函數的:cgt和brfalse。這個指令檢查了第1個值(即變量i)是否小於等於第2個值。若是是,它就會指示程序跳轉到標號IL_0002。若是不是,程序就移動到下一個指令。
所以,像ble這樣的指令使咱們的工做更加容易,由於咱們沒必要再次使用cgt和brfalse指令。
在C#中,while結構的條件出如今頂部,可是條件的代碼出如今底部。在轉換爲IL時,在while結構體之間被執行的代碼,會被放置在條件代碼之上。
a.cs
class zzz
{
static int i = 1;
public static void Main()
{
for ( i = 1; i <= 2 ; i++)
{
System.Console.WriteLine(i);
}
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends System.Object
{
.field private static int32 i
.method public hidebysig static void vijay() il managed
{
.entrypoint
ldc.i4.1
stsfld int32 zzz::i
br.s IL_001e
IL_0008: ldsfld int32 zzz::i
call void [mscorlib]System.Console::WriteLine(int32)
ldsfld int32 zzz::i
ldc.i4.1
add
stsfld int32 zzz::i
IL_001e: ldsfld int32 zzz::i
ldc.i4.2
ble.s IL_0008
ret
}
.method public hidebysig specialname rtspecialname static void .cctor() il managed
{
ldc.i4.s 1
stsfld int32 zzz::i
ret
}
}
Output
1
2
老生常談,while和for結構提供了相同的功能,能夠互相轉換。
在for循環中,第1個分號以前的代碼只能被執行一次。所以,將要被初始化的變量i,位於循環的外面。而後,咱們無條件跳轉到標號IL_001e,以檢查i的值是否小於2。若是結果爲TRUE,那麼代碼跳轉到標號IL_0008,它是for語句這段代碼的開始點。
使用WriteLine函數打印出i的值。此後,變量i的值每次增加1,而條件會被一次又一次的檢查。
a.cs
public class zzz
{
public static void Main()
{
int i;
i = 1;
while ( i <= 2)
{
System.Console.Write(i);
i++;
}
i = 1;
do
{
System.Console.Write(i);
i++;
} while ( i <= 2);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends [mscorlib]System.Object
{
.method public hidebysig static void vijay() il managed
{
.entrypoint
.locals (int32 V_0)
ldc.i4.1
stloc.0
br.s IL_000e
IL_0004: ldloc.0
call void [mscorlib]System.Console::Write(int32)
ldloc.0
ldc.i4.1
add
stloc.0
IL_000e: ldloc.0
ldc.i4.2
ble.s IL_0004
ldc.i4.1
stloc.0
IL_0014: ldloc.0
call void [mscorlib]System.Console::Write(int32)
ldloc.0
ldc.i4.1
add
stloc.0
ldloc.0
ldc.i4.2
ble.s IL_0014
ret
}
}
Output
1212
在C#中,do循環和while循環之間的區別是——條件在什麼位置被檢查。
l 在do-while循環中,條件會在循環的結尾被檢查。這意味着循環中的代碼至少會被調用一次。
l 在while循環中,條件會在循環的開始被檢查。所以,代碼可能歷來都不會被執行。
不論是哪一種狀況,咱們都把值1放到棧上,並初始化變量i或V_1。
l 在while循環中,咱們首先跳轉到標號IL_000e,也就是檢查條件——變量是否小於等於2——的地方。若是返回TRUE,咱們就跳轉到標號IL_0004。
l 在do-while循環中,首先Write函數會被執行,隨後包括在花括號{}中的剩餘代碼將會被執行。當到達花括號{}中代碼的最後一行時,條件纔會被檢查。
所以,在IL中寫一個do-while循環要比寫一個while循環簡單得多,由於條件會直接在循環的末尾被檢查。
a.cs
public class zzz
{
public static void Main()
{
int i ;
for ( i = 1; i<= 10 ; i++)
{
if ( i == 2)
break;
System.Console.WriteLine(i);
}
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends [mscorlib]System.Object
{
.method public hidebysig static void vijay() il managed
{
.entrypoint
.locals (int32 V_0)
ldc.i4.1
stloc.0
br.s IL_0014
IL_0004: ldloc.0
ldc.i4.2
bne.un.s IL_000a
br.s IL_0019
IL_000a: ldloc.0
call void [mscorlib]System.Console::WriteLine(int32)
ldloc.0
ldc.i4.1
add
stloc.0
IL_0014: ldloc.0
ldc.i4.s 10
ble.s IL_0004
IL_0019: ret
}
}
Output
1
break語句強迫退出for循環、while循環或do-while循環。和往常同樣,咱們跳轉到標號IL_0014,也就是變量V_0或i的值被放置到棧上的地方。而後,咱們把條件值10放到棧上,並使用ble.s指令檢查i是小於仍是大於10。
a.cs
public class zzz
{
public static void Main()
{
int i ;
for ( i = 1; i<= 10 ; i++)
{
if ( i == 2)
continue;
System.Console.WriteLine(i);
}
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends [mscorlib]System.Object
{
.method public hidebysig static void vijay() il managed
{
.entrypoint
.locals (int32 V_0)
ldc.i4.1
stloc.0
br.s IL_0014
IL_0004: ldloc.0
ldc.i4.2
bne.un.s IL_000a
br.s IL_0010
IL_000a: ldloc.0
call void [mscorlib]System.Console::WriteLine(int32)
IL_0010: ldloc.0
ldc.i4.1
add
stloc.0
IL_0014: ldloc.0
ldc.i4.s 10
ble.s IL_0004
ret
}
}
continue語句控制for循環到達結束位置。當if語句結果爲TRUE時,程序將繞過WriteLine函數而跳轉到循環的結束。而後,代碼將在標號IL_0010繼續執行,這裏,變量V_0的值會增長1。
在break和continue語句之間的主要區別以下所示:
l 在break語句中,程序會跳出循環。
l 在continue語句中,程序繞過剩下的語句,跳轉到循環的結尾。
goto語句也能到達相同的功能。從而,break語句、continue語句或goto語句,在轉換爲IL時,都會被轉換爲相同的br指令。
下面的程序示範了C#中的goto語句會被直接轉換爲IL中的br語句。
a.cs
public class zzz
{
public static void Main()
{
goto aa;
aa: ;
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends [mscorlib]System.Object
{
.method public hidebysig static void vijay() il managed
{
.entrypoint
br.s IL_0002
IL_0002: ret
}
}
C#中一個簡單的goto語句,會被轉換爲IL中的br指令。在C#這樣的語言中使用goto被認爲是不恰當的,可是,它在IL中的等價物——br指令,對於實現諸如if語句、循環等各類結構而言,倒是極其有用的。所以,在編程語言中的禁忌,在IL中倒是極其有用的。
a.cs
public class zzz
{
public static void Main()
{
int j;
for ( int i = 1; i <= 2 ; i++)
System.Console.Write(i);
}
}
a.il
.assembly mukhi {}
.class private auto ansi zzz extends [mscorlib]System.Object
{
.method public hidebysig static void vijay() il managed
{
.entrypoint
.locals (int32 V_0,int32 V_1)
ldc.i4.1
stloc.1
br.s IL_000e
IL_0004: ldloc.1
call void [mscorlib]System.Console::Write(int32)
ldloc.1
ldc.i4.1
add
stloc.1
IL_000e: ldloc.1
ldc.i4.2
ble.s IL_0004
ret
}
}
Output
12
這個例子解釋了for循環。咱們在Main函數中建立了一個變量j,在for語句中建立了一個變量i。在C#中,這個變量i只在for循環中是可見的。所以,這個變量的做用域是受限制的。
可是,轉換到IL時,全部的變量都具備相同的做用域。這是由於,變量做用域的概念對IL而言有所不一樣。所以,取決於C#編譯器所執行的變量做用域規則。所以,咱們能判斷出——全部的變量在IL中具備相同的做用域和可見性。