IL程式碼底層執行機制之迴圈處理 (轉)
IL程式碼底層執行機制之
迴圈處理
劉強
cambest@sohu.com
年10月22日
上一篇文章我們討論了IL程式碼的基本執行機制。在這篇文章裡,我們將討論IL程式碼是怎樣處理中的迴圈。例子還涉及到陣列處理,以及一些新涉及到的指令。雖然已經有人進行過相關問題的研究,我也看過幾篇有關文章,不過我認為他們描述得並不是很清楚,所以在這裡我藉機重新整理成文,希望對大家學習理解會有所幫助,同時也希望對研究虛擬機器機制的有關設計人員有所幫助。
同樣,這裡也先給出C#程式碼,然後再讓我們詳細研究其編譯後的IL程式碼。下面是C#程式碼,它含有三個迴圈,分別是for、while、foreach迴圈:
public int LTest()
{
int i=3;
int j=9;
int s=0;
int k; file://以上各條語句定義變數並進行初始化
for(k=0;k<=i;k++)
{
s+=k;
} file://for迴圈塊
k=0;
while(k { s+=k; k++; } file://while迴圈塊 int[] array={2,3,4,5,6,7,8,9}; foreach(int a in array) { s+=a; } file://foreach迴圈塊 return s; } 在這裡,我們要做的是搞清楚C#是把源翻譯成怎樣的IL程式碼以實現迴圈處理的,或者說如何用IL語言實現C#語言中的迴圈。這對我們深入理解C#語言特性是很有幫助的。當然僅僅這一點還不夠,以後我還會介紹更多的有關方面的問題。 首先讓我們看看這個被編譯成什麼樣的IL程式碼: .method public hidebysig instance int32 LoopTest() cil managed { // 程式碼大小 101 (0x65) .maxstack 3 .locals init ([0] int32 i, [1] int32 j, [2] int32 s, [3] int32 k, [4] int32[] 'array', [5] int32 a, [6] int32 CS$00000003$00000000, file://跟函式返回值型別相同的區域性變數,由編譯器維護,專門用於返回 //值。如果函式為void型,則無此變數。 [7] int32[] CS$00000007$00000001, file://區域性變數,儲存陣列引用,用於foreach迴圈。本例中對應‘array’陣列。 [8] int32 CS$00000008$00000002 file://區域性變數,儲存陣列。專用於foreach迴圈,由編譯器維護。 ) IL_0000: ldc.i4.3 IL_0001: stloc.0 IL_0002: ldc.i4.s 9 IL_0004: stloc.1 IL_0005: ldc.i4.0 IL_0006: stloc.2 IL_0007: ldc.i4.0 IL_0008: stloc.3 IL_0009: br.s IL_0013 IL_000b: ldloc.2 IL_000c: ldloc.3 IL_000d: add IL_000e: stloc.2 IL_000f: ldloc.3 IL_0010: ldc.i4.1 IL_0011: add IL_0012: stloc.3 IL_0013: ldloc.3 IL_0014: ldloc.0 IL_0015: ble.s IL_000b IL_0017: ldc.i4.0 IL_0018: stloc.3 IL_0019: br.s IL_0023 IL_001b: ldloc.2 IL_001c: ldloc.3 IL_001d: add IL_001e: stloc.2 IL_001f: ldloc.3 IL_0020: ldc.i4.1 IL_0021: add IL_0022: stloc.3 IL_0023: ldloc.3 IL_0024: ldloc.1 IL_0025: blt.s IL_001b IL_0027: ldc.i4.8 IL_0028: newarr [mrlib]System.Int32 file://建立長度為8的System.Int32陣列。可以看出陣列元素被對映到Int32類。 IL_002d: dup IL_002e: ldtoken field valuetype ' ' IL_0033: call void [mscorlib] System.Runtime.CompilerServices.RuntimeHelpers:: InitializeArray(class[mscorlib]System.Array, valuetype [mscorlib] System.RuntimeFieldHandle) IL_0038: stloc.s 'array' IL_003a: ldloc.s 'array' IL_003c: stloc.s CS$00000007$00000001 IL_003e: ldc.i4.0 IL_003f: stloc.s CS$00000008$00000002 IL_0041: br.s IL_0055 IL_0043: ldloc.s CS$00000007$00000001 IL_0045: ldloc.s CS$00000008$00000002 IL_0047: ldelem.i4 IL_0048: stloc.s a IL_004a: ldloc.2 IL_004b: ldloc.s a IL_004d: add IL_004e: stloc.2 IL_004f: ldloc.s CS$00000008$00000002 IL_0051: ldc.i4.1 IL_0052: add IL_0053: stloc.s CS$00000008$00000002 IL_0055: ldloc.s CS$00000008$00000002 IL_0057: ldloc.s CS$00000007$00000001 IL_0059: ldlen IL_005a: conv.i4 IL_005b: blt.s IL_0043 IL_005d: ldloc.2 IL_005e: stloc.s CS$00000003$00000000 IL_0060: br.s IL_0062 IL_0062: ldloc.s CS$00000003$00000000 IL_0064: ret } // end of method Advanced::LoopTest 關於函式話題如.locals init語句等,請參見文章〈函式相關〉。這裡我對其中的一些指令做出解釋,主要是與本文相關的條件轉移指令(b*.s)等。其他指令以後我會作適當的介紹。如下所示: 指令
意義
記憶方法(*)
br.s
絕對跳轉,相當於jmp
blt.s
小於轉
Lower Than
ble.s
小於等於轉
Lower or Equals
ldlen
取得陣列長度
ldelem.i4
根據索引取得陣列項
這裡我們可以看到 .locals init偽指令給出了同源程式相同變數名稱。這是因為在反時,相同目錄下有資訊(*.p),否則的我們看到的結果變數以V_x形式(如V_1、V_2等)表示。有關函式區域性變數的話題,請參見《函式相關》一文。 如果你有彙編,可能都熟悉怎樣實現迴圈控制。如,要實現從10加至100的功能,我們可能會這樣做: mov ecx, 100 file://ecx暫存器存放迴圈計數 xor eax, eax file://給eax和標誌暫存器清零 loop: add eax, ecx file://實現相加並將結果存eax dec ecx file://計數減一 cpr: cmp ecx, 9 file://判斷 ecx>=10 或 ecx>9 jg loop file://如果判斷結果為真(大於)的話,則轉loop 這跟高階語言(C/C++//C#)不一樣,for迴圈中的迴圈條件在程式首部給出,而順序的低階語言如MASM都習慣是在迴圈末尾測試迴圈條件的。那麼C#編譯器又是怎樣處理C#迴圈條件位置與一般彙編中迴圈條件測試語句位置的不一致,用IL來實現迴圈條件檢測並正確實現迴圈的呢?首先,在這裡我要說明,在順序執行的組合語言中,測試迴圈條件是完全可以放在迴圈首部的。如上例的IL版為: .locals init([0] int32 eax, [1] int32 ecx,[2] int32 RET_VAL) ldc.i4 100 stloc.1 file://mov ecx, 100 ldc.i4.0 stloc.0 file://xor eax, eax 或 move eax, 0 L_0000: ldloc.1 ldc.i4 10 file:// blt.s L_0003 // ecx < 10 ? Yes-> jmp L_0001 :No -> go on L_0001: ldloc.0 ldloc.1 add stloc.0 file://這幾句實現 eax=eax+ecx ldloc.1 ldc.i4.1 sub stloc.1 file://這幾句實現 ecx=ecx-1 L_0002: br.s L_0000 L_0003: … 其次,我要說明不這樣做的理由。理由有二,其一是破壞了正常邏輯,這一點是從編譯器層面上來說的。比如,對於語句if(k 下面我們來看看例子中三種迴圈的具體實現。有關IL程式碼的基本執行機制,請參看《IL程式碼底層執行機制》一文。IL_0027到IL_003f是進行陣列初始化的,比較難懂一點。我們暫且放下,以後我還會介紹。 1. for語句 可以看出,程式段中IL_0000到IL_0008是執行變數初始化工作的。從IL_0009開始,就是迴圈體了。IL_00009是一條直接(絕對)跳轉語句,跳轉到IL00_13。我們看看這裡的內容: IL_0013: ldloc.3 IL_0014: ldloc.0 IL_0015: ble.s IL_000b 載入區域性變數3(也即k),再載入區域性變數0(即j)。後面是一條比較轉移指令ble.s。不難看出,這三條語句用於比較k與j的大小。如果比較結果為真(小於等於),則轉入迴圈體內(IL_000b處),為假則繼續執行直接出迴圈體。過程如行雲流水,簡潔直觀,不多作解釋。從這裡我們也可以看出,for語句是先進行條件測試,後執行迴圈體的。 ... ldloc.3 ldloc.0 ble.s
top
…
top
k
…
top
j
k
…
top
…
load指令將變數逐個加至程式棧。ble.s指令進行比較。值得我們注意的是,ble.s還要進行清棧操作。不僅是ble.s,其他條件轉移指令也都是如此。 2. foreach語句 foreach語句和for語句處理過程大致相當。我們感興趣的是foreach怎樣處理邊界條件。從IL0041開始,就進入了foreach迴圈體。同樣一條直接跳轉指令把我們帶到了IL_0055處,讓我們看看這裡是什麼。 IL_0055: ldloc.s CS$00000008$00000002 IL_0057: ldloc.s CS$00000007$00000001 IL_0059: ldlen IL_005a: conv.i4 IL_005b: blt.s IL_0043 前面我介紹過CS$00000008$00000002是儲存陣列索引的,CS$00000007$00000001是陣列‘array’的引用。IL0055到IL005b的過程操作是這樣的:首先向程式棧載入當前索引,再載入陣列引用(32位的HashCode)。ldlen指令根據陣列引用取得陣列長度(64位長整型)並將之轉換成32為整型,將索引與此長度進行比較。如果小於,則轉入迴圈體繼續執行;否則出迴圈。從這裡我們也可以看出,IL對陣列操作給予了很強的支援,直接為它提供了相應的指令。 3. while語句和do-while語句 從例子中可以看出,while和for迴圈處理方式是一樣的。這裡沒有給出do-while例子,但是可以想見它跟for語句處理是一樣的。但是,do-while迴圈要注意,在其迴圈首部沒有像for和foreach迴圈那樣的直接跳轉指令跳轉到條件測試程式碼處。因此,不管什麼情況,do-while迴圈都是至少執行一次的。 在這篇文章中,我介紹了幾條有關條件跳轉指令,以及C#編譯器是怎樣處理C#語言中的迴圈的。其實,本文不能完全算是IL底層機制相關文章,但是要深入瞭解IL,這點基礎還是必要的。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-963560/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- JavaScript執行機制-node事件迴圈JavaScript事件
- JavaScript 閉包的底層執行機制JavaScript
- PHP底層的執行機制與原理PHP
- JavaScript閉包的底層執行機制JavaScript
- PHP 底層的執行機制與原理PHP
- 【執行機制】 JavaScript的事件迴圈機制總結 eventLoopJavaScript事件OOP
- 【深入 PHP】PHP7 底層執行機制PHP
- JavaScript 執行機制-瀏覽器事件迴圈JavaScript瀏覽器事件
- PHP 底層的執行機制與原理解析PHP
- EBS 迴圈處理塊記錄的程式碼
- php底層原理之垃圾回收機制PHP
- Java 底層機制Java
- 【轉】javascript執行機制之this詳解JavaScript
- JavaScript的程式碼執行機制JavaScript
- spring原始碼深度解析— IOC 之 迴圈依賴處理Spring原始碼
- JavaScript事件迴圈機制JavaScript事件
- JavaScript 事件迴圈機制JavaScript事件
- 【轉】Promise迴圈序列執行寫法Promise
- JavaScript執行機制深層剖析JavaScript
- Js 執行機制深層剖析JS
- 細說計算機底層整型編碼機制計算機
- 深入理解JavaScript之徹底弄懂JsEventLoop執行機制JavaScriptJSOOP
- 迴圈取值並處理示例
- (轉)Qt 的執行緒與事件迴圈QT執行緒事件
- javascript事件迴圈機制EventLoopJavaScript事件OOP
- javascript之事件迴圈機制JavaScript事件
- js--事件迴圈機制JS事件
- 執行迴路RunLoop型別機制OOP型別
- 非同步程式設計之事件迴圈機制非同步程式設計事件
- tomcat連線處理機制和執行緒模型Tomcat執行緒模型
- 時間遞增迴圈執行指令碼指令碼
- Runloop-執行迴圈OOP
- windows批處理之三:for迴圈Windows
- C#執行外部程式之執行DOS命令和批處理C#
- 事件迴圈機制的那些事事件
- ES6 事件迴圈機制事件
- 深度解析Java執行緒池的異常處理機制Java執行緒
- MyBatis原始碼分析之核心處理層MyBatis原始碼