一、前言
前幾天群里有位水友提問:”C#中,當一個方法所傳入的參數是一個靜態字段的時候,程序是直接到靜態字段拿數據還是從復制的函數棧中拿數據“。其實很明顯,這和方法參數的傳遞方式有關,如果是引用傳遞的話,肯定是會去靜態字段直接拿值的;如果方法是以傳值的方式使用參數的話,一定是從復制的棧中拿值的。
但是這位水友就是不相信這個結論(后來發現,這貨整一個杠精啊!天天在群里跟人抬杠~),這個時候我忽然想到了可以反向看一下C#的IL代碼,來了解一下程序運行的過程。的確,當我們對運行結果有異議的時候,可以通過IL代碼透過表面看本質。
二、IL簡介
在我們分析查看IL之前首先要了解下什么是IL?IL的全稱是Intermediate Language (IL)即將.NET代碼轉化為機器語言的一個中間語言的縮寫。在一定程度上,我們可以將其理解為偽匯編語言。我們在使用.NET框架中的C#、VB.NET、F#等語言的時候,編譯過程並不是像C/C++一樣直接編譯出原生代碼,而是編譯成IL中間語言。通過IL中間語言這種方式,可以實現跨平台、提高程序靈活性等多種優點。
下面我們以C#語言為例,大致了解了解一下我們的源代碼是如何編譯成IL語言,繼而運行在電腦上面的。

圖1:.NET語言編譯過程示意圖
上圖為C#語言的編譯運行過程示意圖。首先編譯器將我們編寫好的源代碼編譯成IL中間語言,這些IL中間語言的主要內容是一些元數據和中間語言指令。然后再由我們的JIT編譯器加載這些IL中間語言,JIT編譯器會根據系統環境將IL中間語言指令轉換為機器碼,繼而運行在不同的目標平台上,實現跨平台功能。(JIT編譯器將IL中間語言即時編譯成原生語言的過程和解釋性語言的讀取一條執行一條又有些不同,JIT會對編譯結果進行緩存以便下次調取的時候直接使用)這也是為什么有些ASP.NET網站第一次運行時會較慢,而后面的執行速度則會相對快很多的一個原因。
再總結一下上面所說的編譯過程:
-
- 首先,編譯器要編歷源代碼,通過大量的計算生成IL中間代碼,這些代碼並不能直接地被CPU使用,還需要第二步操作;
- 接下來,運行時將這些IL代碼通過JIT編譯器進一步編譯成原生的CPU指令。
在上文中我們提到了一個JIT編譯器,它的全名叫即時編譯器。顧名思義,它是在運行時環境中發生的編譯行為。讀到這里,相信很多朋友可能都會像馬三一樣產生疑問了。相比傳統的直接將源代碼編譯成原生代碼,C#將源代碼編譯成了中間語言不會降低效率嘛?原來直接一步到位的過程,現在偏要拆成兩個部分。這不僅要花費更多的時間、占用更多的內存,還有可能降低性能,那用JIT編譯器的好處到底有什么呢?
其實,使用JIT編譯器的好處多多。如果我們不用JIT即時編譯將編譯后的程序放在運行時中的話,那就只能生成在在某一種CPU平台上運行的原生代碼,如果程序要運行在多種目標平台上的話,就要編譯多種目標平台的原生代碼,這樣C#也就失去了跨平台能力。其次,JIT即時編譯生成原生代碼發生在運行時階段,因此即時編譯器會對生成的原生代碼進行性能優化。經過優化的原生代碼要比不優化的代碼性能好。
在我們的Unity游戲開發中就存在着AOT編譯和JIT編譯兩種編譯方式,以后我們會單獨開篇博客來詳細探討一下這兩種編譯方式的異同,這里就不再贅述了。
三、如何使用ILDasm工具查看IL代碼
上面說了一大堆概念和理論,相信大家早已經技癢,別急,下面,馬三就和大家一起使用ILDasm工具反編譯並查看IL代碼。ILDasm工具一般在我們安裝Visual Studio的時候就已經默認安裝好了,查看IL代碼通常只需下面的幾步操作:
1.首先,需要打開ILDasm工具,點擊電腦桌面的開始,然后在程序中找到對應版本的Visual Studio的目錄,接着在里面找到VS開發人員命令提示,英文版的名稱是:Developer Command Prompt 。點擊打開它,然后在命令行里面輸入ILDasm並回車,就會打開ILDasm工具了。


圖2:VS開發人員命令提示示意圖
2.然后,在ILDasm工具的界面,點擊文件-->打開,然后選擇我們預先編譯出來的.exe文件,ILDasm工具就會自動的幫我們分析出IL代碼及其代碼組織結構,如下圖所示:


圖3:解析出來的IL代碼及相關代碼組織結構
解析出來的IL代碼,會以一些小圖標區分標識出函數、接口等不同的部分,ILDasm中圖標含義如下圖所示:

圖4:ILDasm中圖標含義
通過上面的兩步,我們就可以輕松的反編譯出程序的IL代碼,下面我們通過一個小案例,來解讀一下IL代碼的運行流程以及分析IL代碼的方式。
四、淺析IL代碼
好了,現在讓我們回到博客最初拋出的那個問題上面來:“C#中,當一個方法所傳入的參數是一個靜態字段的時候,程序是直接到靜態字段拿數據還是從復制的函數棧中拿數據?”下面我們就以這個問題為例子,通過反編譯出IL代碼,簡單地驗證一下。
首先貼出我們的C#代碼,很簡單,在兩個方法中分別以傳值和傳遞引用的方式傳入同一個靜態變量:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace ILDemo 8 { 9 10 class Program 11 { 12 public static int n = 0; 13 static void Main(string[] args) 14 { 15 Func1(n); 16 Console.WriteLine(n); 17 Func2(ref n); 18 Console.WriteLine(n); 19 } 20 21 public static void Func1(int n) 22 { 23 n += 5; 24 } 25 26 public static void Func2(ref int n) 27 { 28 n += 5; 29 } 30 } 31 32 }
然后我們對上面的代碼進行編譯,之后再按照第三節的操作,將其反編譯成IL代碼,以下是反編譯出來的IL代碼結構:

圖5:反編譯IL代碼結構
首先讓我們觀察一下Func1反編譯出來的IL代碼,如下圖所示:

圖6:Func1反編譯出來的IL代碼
馬三簡單地給大家分析一下上面的IL代碼的意思:
nop:沒有什么意義;
ldarg.0:將索引為 0 的參數加載到計算堆棧上,也就是把參數 n 放到堆棧上;
ldc.i4.5:將整數值 5 作為 int32 推送到計算堆棧上,因為我們在程序中執行了+5的操作(上面的C#代碼),所以需要先把5推送到堆棧上,以便下一步進行add操作;
add:將兩個值相加並將結果推送到計算堆棧上,這里是將 n和5 相加,然后把結果推送到堆棧上;
Starg.S n:將位於計算堆棧頂部的值存儲在參數槽中的指定索引處,即將n進行存儲
ret:從當前方法返回,並將返回值(如果存在)從調用方的計算堆棧推送到被調用方的計算堆棧上。
接着,我們再來看看Func2反編譯出來的IL代碼:

圖7:Func2反編譯出來的IL代碼
可以看到,因為我們的C#代碼中使用了ref參數,所以在IL代碼中將其翻譯成了int32& n的形式,和C++是不是很類似?
nop:沒有什么意義;
ldarg.0:將索引為 0 的參數加載到計算堆棧上,也就是把參數 n的地址放到堆棧上;
ldind.i4:將 int32 類型的值作為 int32 間接加載到計算堆棧上
ldc.i4.5:將整數值 5 作為 int32 推送到計算堆棧上,因為我們在程序中執行了+5的操作(上面的C#代碼),所以需要先把5推送到堆棧上,以便下一步進行add操作;
add:將兩個值相加並將結果推送到計算堆棧上,這里是將 n和5 相加,然后把結果推送到堆棧上;
stind.i4:在所提供的地址存儲 int32 類型的值,即把我們計算出來的結果,再存儲回靜態變量n的地址里面;
ret:從當前方法返回,並將返回值(如果存在)從調用方的計算堆棧推送到被調用方的計算堆棧上。
通過比較,我們可以很明顯地發現由於參數傳遞的方式不同,Func1和Func2調用的IL指令也不近相同,最后讓我們再看一下Main函數的執行過程再做分析。

圖8:Main函數反編譯出來的IL代碼
反編譯出來的Main函數IL指令執行流程如下:
nop:沒有什么意義;
Ldsfld:將靜態字段的值推送到計算堆棧上,這里就是把n的值推送到計算堆棧上;
call:調用由傳遞的方法說明符指示的方法,也就是調用了Func1函數;
nop:沒有什么意義;
Ldsfld:將靜態字段的值推送到計算堆棧上,這里就是把n的值推送到計算堆棧上;
call:調用由傳遞的方法說明符指示的方法,調用打印方法,將n的值輸出到控制台
nop:沒有什么意義;
Ldsflda:將靜態字段的地址推送到計算堆棧上,就是把靜態變量n在內存中的地址推送到計算堆棧上;
call:調用由傳遞的方法說明符指示的方法,也就是調用了Func2函數;
nop:沒有什么意義;
Ldsfld:將靜態字段的值推送到計算堆棧上,這里就是把n的值推送到計算堆棧上;
call:調用由傳遞的方法說明符指示的方法,調用打印方法,將n的值輸出到控制台
經過上面的一系列漫長地分析,我們可以得出結論:C#中,當一個方法所傳入的參數是一個靜態字段的時候,如果是引用傳遞的話,肯定是會去靜態字段直接拿值的;如果方法是以傳值的方式使用參數的話,一定是從復制的棧中拿值的。“”
五、IL代碼指令對照表
為了方便大家對照查看IL代碼,理解IL指令的意義,馬三在這里給大家提供了一個詳細的IL指令對照表(Zery提供),原版的IL指令對照表大家可以到MSDN上面查看,附上鏈接。
| 名稱 | 說明 |
| Add | 將兩個值相加並將結果推送到計算堆棧上。 |
| Add.Ovf | 將兩個整數相加,執行溢出檢查,並且將結果推送到計算堆棧上。 |
| Add.Ovf.Un | 將兩個無符號整數值相加,執行溢出檢查,並且將結果推送到計算堆棧上。 |
| And | 計算兩個值的按位“與”並將結果推送到計算堆棧上。 |
| Arglist | 返回指向當前方法的參數列表的非托管指針。 |
| Beq | 如果兩個值相等,則將控制轉移到目標指令。 |
| Beq.S | 如果兩個值相等,則將控制轉移到目標指令(短格式)。 |
| Bge | 如果第一個值大於或等於第二個值,則將控制轉移到目標指令。 |
| Bge.S | 如果第一個值大於或等於第二個值,則將控制轉移到目標指令(短格式)。 |
| Bge.Un | 當比較無符號整數值或不可排序的浮點型值時,如果第一個值大於第二個值,則將控制轉移到目標指令。 |
| Bge.Un.S | 當比較無符號整數值或不可排序的浮點型值時,如果第一個值大於第二個值,則將控制轉移到目標指令(短格式)。 |
| Bgt | 如果第一個值大於第二個值,則將控制轉移到目標指令。 |
| Bgt.S | 如果第一個值大於第二個值,則將控制轉移到目標指令(短格式)。 |
| Bgt.Un | 當比較無符號整數值或不可排序的浮點型值時,如果第一個值大於第二個值,則將控制轉移到目標指令。 |
| Bgt.Un.S | 當比較無符號整數值或不可排序的浮點型值時,如果第一個值大於第二個值,則將控制轉移到目標指令(短格式)。 |
| Ble | 如果第一個值小於或等於第二個值,則將控制轉移到目標指令。 |
| Ble.S | 如果第一個值小於或等於第二個值,則將控制轉移到目標指令(短格式)。 |
| Ble.Un | 當比較無符號整數值或不可排序的浮點型值時,如果第一個值小於或等於第二個值,則將控制轉移到目標指令。 |
| Ble.Un.S | 當比較無符號整數值或不可排序的浮點值時,如果第一個值小於或等於第二個值,則將控制權轉移到目標指令(短格式)。 |
| Blt | 如果第一個值小於第二個值,則將控制轉移到目標指令。 |
| Blt.S | 如果第一個值小於第二個值,則將控制轉移到目標指令(短格式)。 |
| Blt.Un | 當比較無符號整數值或不可排序的浮點型值時,如果第一個值小於第二個值,則將控制轉移到目標指令。 |
| Blt.Un.S | 當比較無符號整數值或不可排序的浮點型值時,如果第一個值小於第二個值,則將控制轉移到目標指令(短格式)。 |
| Bne.Un | 當兩個無符號整數值或不可排序的浮點型值不相等時,將控制轉移到目標指令。 |
| Bne.Un.S | 當兩個無符號整數值或不可排序的浮點型值不相等時,將控制轉移到目標指令(短格式)。 |
| Box | 將值類轉換為對象引用(O 類型)。 |
| Br | 無條件地將控制轉移到目標指令。 |
| Br.S | 無條件地將控制轉移到目標指令(短格式)。 |
| Break | 向公共語言結構 (CLI) 發出信號以通知調試器已撞上了一個斷點。 |
| Brfalse | 如果 value 為 false、空引用(Visual Basic 中的 Nothing)或零,則將控制轉移到目標指令。 |
| Brfalse.S | 如果 value 為 false、空引用或零,則將控制轉移到目標指令。 |
| Brtrue | 如果 value 為 true、非空或非零,則將控制轉移到目標指令。 |
| Brtrue.S | 如果 value 為 true、非空或非零,則將控制轉移到目標指令(短格式)。 |
| Call | 調用由傳遞的方法說明符指示的方法。 |
| Calli | 通過調用約定描述的參數調用在計算堆棧上指示的方法(作為指向入口點的指針)。 |
| Callvirt | 對對象調用后期綁定方法,並且將返回值推送到計算堆棧上。 |
| Castclass | 嘗試將引用傳遞的對象轉換為指定的類。 |
| Ceq | 比較兩個值。如果這兩個值相等,則將整數值 1 (int32) 推送到計算堆棧上;否則,將 0 (int32) 推送到計算堆棧上。 |
| Cgt | 比較兩個值。如果第一個值大於第二個值,則將整數值 1 (int32) 推送到計算堆棧上;反之,將 0 (int32) 推送到計算堆棧上。 |
| Cgt.Un | 比較兩個無符號的或不可排序的值。如果第一個值大於第二個值,則將整數值 1 (int32) 推送到計算堆棧上;反之,將 0 (int32) 推送到計算堆棧上。 |
| Ckfinite | 如果值不是有限數,則引發 ArithmeticException。 |
| Clt | 比較兩個值。如果第一個值小於第二個值,則將整數值 1 (int32) 推送到計算堆棧上;反之,將 0 (int32) 推送到計算堆棧上。 |
| Clt.Un | 比較無符號的或不可排序的值 value1 和 value2。如果 value1 小於 value2,則將整數值 1 (int32 ) 推送到計算堆棧上;反之,將 0 ( int32 ) 推送到計算堆棧上。 |
| Constrained | 約束要對其進行虛方法調用的類型。 |
| Conv.I | 將位於計算堆棧頂部的值轉換為 native int。 |
| Conv.I1 | 將位於計算堆棧頂部的值轉換為 int8,然后將其擴展(填充)為 int32。 |
| Conv.I2 | 將位於計算堆棧頂部的值轉換為 int16,然后將其擴展(填充)為 int32。 |
| Conv.I4 | 將位於計算堆棧頂部的值轉換為 int32。 |
| Conv.I8 | 將位於計算堆棧頂部的值轉換為 int64。 |
| Conv.Ovf.I | 將位於計算堆棧頂部的有符號值轉換為有符號 native int,並在溢出時引發 OverflowException。 |
| Conv.Ovf.I.Un | 將位於計算堆棧頂部的無符號值轉換為有符號 native int,並在溢出時引發 OverflowException。 |
| Conv.Ovf.I1 | 將位於計算堆棧頂部的有符號值轉換為有符號 int8 並將其擴展為 int32,並在溢出時引發 OverflowException。 |
| Conv.Ovf.I1.Un | 將位於計算堆棧頂部的無符號值轉換為有符號 int8 並將其擴展為 int32,並在溢出時引發 OverflowException。 |
| Conv.Ovf.I2 | 將位於計算堆棧頂部的有符號值轉換為有符號 int16 並將其擴展為 int32,並在溢出時引發 OverflowException。 |
| Conv.Ovf.I2.Un | 將位於計算堆棧頂部的無符號值轉換為有符號 int16 並將其擴展為 int32,並在溢出時引發 OverflowException。 |
| Conv.Ovf.I4 | 將位於計算堆棧頂部的有符號值轉換為有符號 int32,並在溢出時引發 OverflowException。 |
| Conv.Ovf.I4.Un | 將位於計算堆棧頂部的無符號值轉換為有符號 int32,並在溢出時引發 OverflowException。 |
| Conv.Ovf.I8 | 將位於計算堆棧頂部的有符號值轉換為有符號 int64,並在溢出時引發 OverflowException。 |
| Conv.Ovf.I8.Un | 將位於計算堆棧頂部的無符號值轉換為有符號 int64,並在溢出時引發 OverflowException。 |
| Conv.Ovf.U | 將位於計算堆棧頂部的有符號值轉換為 unsigned native int,並在溢出時引發 OverflowException。 |
| Conv.Ovf.U.Un | 將位於計算堆棧頂部的無符號值轉換為 unsigned native int,並在溢出時引發 OverflowException。 |
| Conv.Ovf.U1 | 將位於計算堆棧頂部的有符號值轉換為 unsigned int8 並將其擴展為 int32,並在溢出時引發 OverflowException。 |
| Conv.Ovf.U1.Un | 將位於計算堆棧頂部的無符號值轉換為 unsigned int8 並將其擴展為 int32,並在溢出時引發 OverflowException。 |
| Conv.Ovf.U2 | 將位於計算堆棧頂部的有符號值轉換為 unsigned int16 並將其擴展為 int32,並在溢出時引發 OverflowException。 |
| Conv.Ovf.U2.Un | 將位於計算堆棧頂部的無符號值轉換為 unsigned int16 並將其擴展為 int32,並在溢出時引發 OverflowException。 |
| Conv.Ovf.U4 | 將位於計算堆棧頂部的有符號值轉換為 unsigned int32,並在溢出時引發 OverflowException。 |
| Conv.Ovf.U4.Un | 將位於計算堆棧頂部的無符號值轉換為 unsigned int32,並在溢出時引發 OverflowException。 |
| Conv.Ovf.U8 | 將位於計算堆棧頂部的有符號值轉換為 unsigned int64,並在溢出時引發 OverflowException。 |
| Conv.Ovf.U8.Un | 將位於計算堆棧頂部的無符號值轉換為 unsigned int64,並在溢出時引發 OverflowException。 |
| Conv.R.Un | 將位於計算堆棧頂部的無符號整數值轉換為 float32。 |
| Conv.R4 | 將位於計算堆棧頂部的值轉換為 float32。 |
| Conv.R8 | 將位於計算堆棧頂部的值轉換為 float64。 |
| Conv.U | 將位於計算堆棧頂部的值轉換為 unsigned native int,然后將其擴展為 native int。 |
| Conv.U1 | 將位於計算堆棧頂部的值轉換為 unsigned int8,然后將其擴展為 int32。 |
| Conv.U2 | 將位於計算堆棧頂部的值轉換為 unsigned int16,然后將其擴展為 int32。 |
| Conv.U4 | 將位於計算堆棧頂部的值轉換為 unsigned int32,然后將其擴展為 int32。 |
| Conv.U8 | 將位於計算堆棧頂部的值轉換為 unsigned int64,然后將其擴展為 int64。 |
| Cpblk | 將指定數目的字節從源地址復制到目標地址。 |
| Cpobj | 將位於對象(&、* 或 native int 類型)地址的值類型復制到目標對象(&、* 或 native int 類型)的地址。 |
| Div | 將兩個值相除並將結果作為浮點(F 類型)或商(int32 類型)推送到計算堆棧上。 |
| Div.Un | 兩個無符號整數值相除並將結果 ( int32 ) 推送到計算堆棧上。 |
| Dup | 復制計算堆棧上當前最頂端的值,然后將副本推送到計算堆棧上。 |
| Endfilter | 將控制從異常的 filter 子句轉移回公共語言結構 (CLI) 異常處理程序。 |
| Endfinally | 將控制從異常塊的 fault 或 finally 子句轉移回公共語言結構 (CLI) 異常處理程序。 |
| Initblk | 將位於特定地址的內存的指定塊初始化為給定大小和初始值。 |
| Initobj | 將位於指定地址的值類型的每個字段初始化為空引用或適當的基元類型的 0。 |
| Isinst | 測試對象引用(O 類型)是否為特定類的實例。 |
| Jmp | 退出當前方法並跳至指定方法。 |
| Ldarg | 將參數(由指定索引值引用)加載到堆棧上。 |
| Ldarg.0 | 將索引為 0 的參數加載到計算堆棧上。 |
| Ldarg.1 | 將索引為 1 的參數加載到計算堆棧上。 |
| Ldarg.2 | 將索引為 2 的參數加載到計算堆棧上。 |
| Ldarg.3 | 將索引為 3 的參數加載到計算堆棧上。 |
| Ldarg.S | 將參數(由指定的短格式索引引用)加載到計算堆棧上。 |
| Ldarga | 將參數地址加載到計算堆棧上。 |
| Ldarga.S | 以短格式將參數地址加載到計算堆棧上。 |
| Ldc.I4 | 將所提供的 int32 類型的值作為 int32 推送到計算堆棧上。 |
| Ldc.I4.0 | 將整數值 0 作為 int32 推送到計算堆棧上。 |
| Ldc.I4.1 | 將整數值 1 作為 int32 推送到計算堆棧上。 |
| Ldc.I4.2 | 將整數值 2 作為 int32 推送到計算堆棧上。 |
| Ldc.I4.3 | 將整數值 3 作為 int32 推送到計算堆棧上。 |
| Ldc.I4.4 | 將整數值 4 作為 int32 推送到計算堆棧上。 |
| Ldc.I4.5 | 將整數值 5 作為 int32 推送到計算堆棧上。 |
| Ldc.I4.6 | 將整數值 6 作為 int32 推送到計算堆棧上。 |
| Ldc.I4.7 | 將整數值 7 作為 int32 推送到計算堆棧上。 |
| Ldc.I4.8 | 將整數值 8 作為 int32 推送到計算堆棧上。 |
| Ldc.I4.M1 | 將整數值 -1 作為 int32 推送到計算堆棧上。 |
| Ldc.I4.S | 將提供的 int8 值作為 int32 推送到計算堆棧上(短格式)。 |
| Ldc.I8 | 將所提供的 int64 類型的值作為 int64 推送到計算堆棧上。 |
| Ldc.R4 | 將所提供的 float32 類型的值作為 F (float) 類型推送到計算堆棧上。 |
| Ldc.R8 | 將所提供的 float64 類型的值作為 F (float) 類型推送到計算堆棧上。 |
| Ldelem | 按照指令中指定的類型,將指定數組索引中的元素加載到計算堆棧的頂部。 |
| Ldelem.I | 將位於指定數組索引處的 native int 類型的元素作為 native int 加載到計算堆棧的頂部。 |
| Ldelem.I1 | 將位於指定數組索引處的 int8 類型的元素作為 int32 加載到計算堆棧的頂部。 |
| Ldelem.I2 | 將位於指定數組索引處的 int16 類型的元素作為 int32 加載到計算堆棧的頂部。 |
| Ldelem.I4 | 將位於指定數組索引處的 int32 類型的元素作為 int32 加載到計算堆棧的頂部。 |
| Ldelem.I8 | 將位於指定數組索引處的 int64 類型的元素作為 int64 加載到計算堆棧的頂部。 |
| Ldelem.R4 | 將位於指定數組索引處的 float32 類型的元素作為 F 類型(浮點型)加載到計算堆棧的頂部。 |
| Ldelem.R8 | 將位於指定數組索引處的 float64 類型的元素作為 F 類型(浮點型)加載到計算堆棧的頂部。 |
| Ldelem.Ref | 將位於指定數組索引處的包含對象引用的元素作為 O 類型(對象引用)加載到計算堆棧的頂部。 |
| Ldelem.U1 | 將位於指定數組索引處的 unsigned int8 類型的元素作為 int32 加載到計算堆棧的頂部。 |
| Ldelem.U2 | 將位於指定數組索引處的 unsigned int16 類型的元素作為 int32 加載到計算堆棧的頂部。 |
| Ldelem.U4 | 將位於指定數組索引處的 unsigned int32 類型的元素作為 int32 加載到計算堆棧的頂部。 |
| Ldelema | 將位於指定數組索引的數組元素的地址作為 & 類型(托管指針)加載到計算堆棧的頂部。 |
| Ldfld | 查找對象中其引用當前位於計算堆棧的字段的值。 |
| Ldflda | 查找對象中其引用當前位於計算堆棧的字段的地址。 |
| Ldftn | 將指向實現特定方法的本機代碼的非托管指針(native int 類型)推送到計算堆棧上。 |
| Ldind.I | 將 native int 類型的值作為 native int 間接加載到計算堆棧上。 |
| Ldind.I1 | 將 int8 類型的值作為 int32 間接加載到計算堆棧上。 |
| Ldind.I2 | 將 int16 類型的值作為 int32 間接加載到計算堆棧上。 |
| Ldind.I4 | 將 int32 類型的值作為 int32 間接加載到計算堆棧上。 |
| Ldind.I8 | 將 int64 類型的值作為 int64 間接加載到計算堆棧上。 |
| Ldind.R4 | 將 float32 類型的值作為 F (float) 類型間接加載到計算堆棧上。 |
| Ldind.R8 | 將 float64 類型的值作為 F (float) 類型間接加載到計算堆棧上。 |
| Ldind.Ref | 將對象引用作為 O(對象引用)類型間接加載到計算堆棧上。 |
| Ldind.U1 | 將 unsigned int8 類型的值作為 int32 間接加載到計算堆棧上。 |
| Ldind.U2 | 將 unsigned int16 類型的值作為 int32 間接加載到計算堆棧上。 |
| Ldind.U4 | 將 unsigned int32 類型的值作為 int32 間接加載到計算堆棧上。 |
| Ldlen | 將從零開始的、一維數組的元素的數目推送到計算堆棧上。 |
| Ldloc | 將指定索引處的局部變量加載到計算堆棧上。 |
| Ldloc.0 | 將索引 0 處的局部變量加載到計算堆棧上。 |
| Ldloc.1 | 將索引 1 處的局部變量加載到計算堆棧上。 |
| Ldloc.2 | 將索引 2 處的局部變量加載到計算堆棧上。 |
| Ldloc.3 | 將索引 3 處的局部變量加載到計算堆棧上。 |
| Ldloc.S | 將特定索引處的局部變量加載到計算堆棧上(短格式)。 |
| Ldloca | 將位於特定索引處的局部變量的地址加載到計算堆棧上。 |
| Ldloca.S | 將位於特定索引處的局部變量的地址加載到計算堆棧上(短格式)。 |
| Ldnull | 將空引用(O 類型)推送到計算堆棧上。 |
| Ldobj | 將地址指向的值類型對象復制到計算堆棧的頂部。 |
| Ldsfld | 將靜態字段的值推送到計算堆棧上。 |
| Ldsflda | 將靜態字段的地址推送到計算堆棧上。 |
| Ldstr | 推送對元數據中存儲的字符串的新對象引用。 |
| Ldtoken | 將元數據標記轉換為其運行時表示形式,並將其推送到計算堆棧上。 |
| Ldvirtftn | 將指向實現與指定對象關聯的特定虛方法的本機代碼的非托管指針(native int 類型)推送到計算堆棧上。 |
| Leave | 退出受保護的代碼區域,無條件將控制轉移到特定目標指令。 |
| Leave.S | 退出受保護的代碼區域,無條件將控制轉移到目標指令(縮寫形式)。 |
| Localloc | 從本地動態內存池分配特定數目的字節並將第一個分配的字節的地址(瞬態指針,* 類型)推送到計算堆棧上。 |
| Mkrefany | 將對特定類型實例的類型化引用推送到計算堆棧上。 |
| Mul | 將兩個值相乘並將結果推送到計算堆棧上。 |
| Mul.Ovf | 將兩個整數值相乘,執行溢出檢查,並將結果推送到計算堆棧上。 |
| Mul.Ovf.Un | 將兩個無符號整數值相乘,執行溢出檢查,並將結果推送到計算堆棧上。 |
| Neg | 對一個值執行求反並將結果推送到計算堆棧上。 |
| Newarr | 將對新的從零開始的一維數組(其元素屬於特定類型)的對象引用推送到計算堆棧上。 |
| Newobj | 創建一個值類型的新對象或新實例,並將對象引用(O 類型)推送到計算堆棧上。 |
| Nop | 如果修補操作碼,則填充空間。盡管可能消耗處理周期,但未執行任何有意義的操作。 |
| Not | 計算堆棧頂部整數值的按位求補並將結果作為相同的類型推送到計算堆棧上。 |
| Or | 計算位於堆棧頂部的兩個整數值的按位求補並將結果推送到計算堆棧上。 |
| Pop | 移除當前位於計算堆棧頂部的值。 |
| Prefix1 | 基礎結構。此指令為保留指令。 |
| Prefix2 | 基礎結構。此指令為保留指令。 |
| Prefix3 | 基礎結構。此指令為保留指令。 |
| Prefix4 | 基礎結構。此指令為保留指令。 |
| Prefix5 | 基礎結構。此指令為保留指令。 |
| Prefix6 | 基礎結構。此指令為保留指令。 |
| Prefix7 | 基礎結構。此指令為保留指令。 |
| Prefixref | 基礎結構。此指令為保留指令。 |
| Readonly | 指定后面的數組地址操作在運行時不執行類型檢查,並且返回可變性受限的托管指針。 |
| Refanytype | 檢索嵌入在類型化引用內的類型標記。 |
| Refanyval | 檢索嵌入在類型化引用內的地址(& 類型)。 |
| Rem | 將兩個值相除並將余數推送到計算堆棧上。 |
| Rem.Un | 將兩個無符號值相除並將余數推送到計算堆棧上。 |
| Ret | 從當前方法返回,並將返回值(如果存在)從調用方的計算堆棧推送到被調用方的計算堆棧上。 |
| Rethrow | 再次引發當前異常。 |
| Shl | 將整數值左移(用零填充)指定的位數,並將結果推送到計算堆棧上。 |
| Shr | 將整數值右移(保留符號)指定的位數,並將結果推送到計算堆棧上。 |
| Shr.Un | 將無符號整數值右移(用零填充)指定的位數,並將結果推送到計算堆棧上。 |
| Sizeof | 將提供的值類型的大小(以字節為單位)推送到計算堆棧上。 |
| Starg | 將位於計算堆棧頂部的值存儲到位於指定索引的參數槽中。 |
| Starg.S | 將位於計算堆棧頂部的值存儲在參數槽中的指定索引處(短格式)。 |
| Stelem | 用計算堆棧中的值替換給定索引處的數組元素,其類型在指令中指定。 |
| Stelem.I | 用計算堆棧上的 native int 值替換給定索引處的數組元素。 |
| Stelem.I1 | 用計算堆棧上的 int8 值替換給定索引處的數組元素。 |
| Stelem.I2 | 用計算堆棧上的 int16 值替換給定索引處的數組元素。 |
| Stelem.I4 | 用計算堆棧上的 int32 值替換給定索引處的數組元素。 |
| Stelem.I8 | 用計算堆棧上的 int64 值替換給定索引處的數組元素。 |
| Stelem.R4 | 用計算堆棧上的 float32 值替換給定索引處的數組元素。 |
| Stelem.R8 | 用計算堆棧上的 float64 值替換給定索引處的數組元素。 |
| Stelem.Ref | 用計算堆棧上的對象 ref 值(O 類型)替換給定索引處的數組元素。 |
| Stfld | 用新值替換在對象引用或指針的字段中存儲的值。 |
| Stind.I | 在所提供的地址存儲 native int 類型的值。 |
| Stind.I1 | 在所提供的地址存儲 int8 類型的值。 |
| Stind.I2 | 在所提供的地址存儲 int16 類型的值。 |
| Stind.I4 | 在所提供的地址存儲 int32 類型的值。 |
| Stind.I8 | 在所提供的地址存儲 int64 類型的值。 |
| Stind.R4 | 在所提供的地址存儲 float32 類型的值。 |
| Stind.R8 | 在所提供的地址存儲 float64 類型的值。 |
| Stind.Ref | 存儲所提供地址處的對象引用值。 |
| Stloc | 從計算堆棧的頂部彈出當前值並將其存儲到指定索引處的局部變量列表中。 |
| Stloc.0 | 從計算堆棧的頂部彈出當前值並將其存儲到索引 0 處的局部變量列表中。 |
| Stloc.1 | 從計算堆棧的頂部彈出當前值並將其存儲到索引 1 處的局部變量列表中。 |
| Stloc.2 | 從計算堆棧的頂部彈出當前值並將其存儲到索引 2 處的局部變量列表中。 |
| Stloc.3 | 從計算堆棧的頂部彈出當前值並將其存儲到索引 3 處的局部變量列表中。 |
| Stloc.S | 從計算堆棧的頂部彈出當前值並將其存儲在局部變量列表中的 index 處(短格式)。 |
| Stobj | 將指定類型的值從計算堆棧復制到所提供的內存地址中。 |
| Stsfld | 用來自計算堆棧的值替換靜態字段的值。 |
| Sub | 從其他值中減去一個值並將結果推送到計算堆棧上。 |
| Sub.Ovf | 從另一值中減去一個整數值,執行溢出檢查,並且將結果推送到計算堆棧上。 |
| Sub.Ovf.Un | 從另一值中減去一個無符號整數值,執行溢出檢查,並且將結果推送到計算堆棧上。 |
| Switch | 實現跳轉表。 |
| Tailcall | 執行后綴的方法調用指令,以便在執行實際調用指令前移除當前方法的堆棧幀。 |
| Throw | 引發當前位於計算堆棧上的異常對象。 |
| Unaligned | 指示當前位於計算堆棧上的地址可能沒有與緊接的 ldind、stind、ldfld、stfld、ldobj、stobj、initblk 或 cpblk 指令的自然大小對齊。 |
| Unbox | 將值類型的已裝箱的表示形式轉換為其未裝箱的形式。 |
| Unbox.Any | 將指令中指定類型的已裝箱的表示形式轉換成未裝箱形式。 |
| Volatile | 指定當前位於計算堆棧頂部的地址可以是易失的,並且讀取該位置的結果不能被緩存,或者對該地址的多個存儲區不能被取消。 |
| Xor | 計算位於計算堆棧頂部的兩個值的按位異或,並且將結果推送到計算堆棧上。 |
引用資料:
http://www.cnblogs.com/zery/p/3368460.html
https://msdn.microsoft.com/en-us/library/812xyxy2
作者:馬三小伙兒
出處:http://www.cnblogs.com/msxh/p/7819645.html
請尊重別人的勞動成果,讓分享成為一種美德,歡迎轉載。另外,文章在表述和代碼方面如有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!
