本章以最小化 C# 程式為切入點,透過 ildasm 反組譯與 ilasm 重組譯,逐行解讀 IL 核心結構:組件宣告、類別定義、方法簽名、堆疊機運作、區域變數、欄位初始化、建構函數鏈、靜態建構函數、新物件創建、this 指標、值型別/參考型別、裝箱/拆箱、參數傳遞、返回值處理。建立「IL 視角」認知 C# 運行時機制的基石。
// C#:
class zzz { static void Main() { WriteLine("hi"); abc(); } static void abc() { WriteLine("bye"); } }
// 精簡 IL (移除元數據、偽指令):
.assembly mukhi {}
.class private auto ansi zzz extends [mscorlib]System.Object
{
.method public hidebysig static void vijay() cil managed
{
.entrypoint
ldstr "hi"
call void [mscorlib]System.Console::WriteLine(string)
call void zzz::abc()
ret
}
.method public hidebysig static void abc() cil managed
{
ldstr "bye"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
ldarg.0
call instance void [mscorlib]System.Object::.ctor()
ret
}
}
| IL 元素 | 含義 | C# 對應 |
|---|---|---|
.assembly | 組件宣告 | 專案屬性/AssemblyInfo |
.class private auto ansi | 私有、自動佈局、ANSI 字串 | class zzz (預設 internal) |
extends System.Object | 繼承基類 | 隱式繼承 object |
.method ... vijay() | 方法定義 | static void Main() |
.entrypoint | 程式進入點 | Main 方法特徵 |
ldstr | 載入字串常量至堆疊 | "hi" |
call | 靜態/非虛方法調用 | WriteLine(), abc() |
ret | 返回 | 方法結束 |
.ctor | 實例建構函數 | 隱式無參建構函數 |
ldarg.0 | 載入第0參數 (this) | 建構函數隱式 this |
// C#: static zzz() { WriteLine("bye"); }
// IL: .method private hidebysig specialname rtspecialname static void .cctor() { ... }
// 輸出順序: bye → hi (靜態建構函數優於 .entrypoint 執行)
.cctor = 靜態建構函數,specialname rtspecialname 標記運行時識別。.cctor,無 this 指標。
// C#: new zzz();
// IL: newobj instance void zzz::.ctor(); pop (丟棄堆疊上的物件引用)
newobj:分配記憶體 → 呼叫 .ctor → 將物件引用壓入堆疊。ldarg.0 = this:實例方法隱式第一參數,指向物件欄位記憶體位置。.ctor 必須顯式 call,且最先執行。
// C#: int i = 6; long j = 7;
// IL:
.locals (int32 V_0, int64 V_1) // 宣告區域變數槽位
ldc.i4.6; stloc.0 // int32 常量 6 入棧 → 存入 V_0
ldc.i4.7; conv.i8; stloc.1 // int32 7 入棧 → 擴展為 int64 → 存入 V_1
V_0, V_1... 槽位索引。ldc.i4 系列:常量入棧,.s 後綴 = 1 位元組編碼 (短形式)。conv.i8:型別轉換指令 (int32 → int64),32 位機器記憶體對齊需求。stloc/ldloc:堆疊頂值 ↔ 區域變數槽位雙向傳輸。
// 靜態欄位初始化 (在 .cctor):
ldc.i4.6; stsfld int32 zzz::i
// 實例欄位初始化 (在 .ctor):
ldarg.0; ldc.i4.7; conv.i8; stfld int64 zzz::j
| 指令 | 目標 | 堆疊操作 |
|---|---|---|
stsfld/ldsfld | 靜態欄位 | 值 ↔ 堆疊 |
stfld/ldfld | 實例欄位 | 需先載入物件引用 (this) |
類別名::欄位名 (靜態) / 物件引用 欄位名 (實例)。
// C#: WriteLine("{0}", 20);
// IL:
ldstr "{0}"
ldc.i4.s 20
stloc.0
ldloca.s V_0 // 取區域變數位址
box [mscorlib]System.Int32 // 值型別 → 參考型別 (裝箱)
call WriteLine(string, object)
ldloca.s 載入位址 (managed pointer),box 需要位址而非值。unbox) 由 WriteLine 內部處理,取出值型別資料。
ldsflda int32 zzz::i // 直接取靜態欄位位址
box [mscorlib]System.Int32
靜態欄位有固定位址,無需區域變數中轉。
// IL 僅 .locals 宣告,無 stloc:
.locals (int32 V_0)
ldloc.0; call WriteLine(int32) // 輸出隨機記憶體殘留值 (如 51380288)
// 實例方法帶參數:
ldloc.0 (this); ldc.i4.s 10; call instance void abc(int32)
// 靜態方法返回值:
call int32 abc() // 返回值壓入堆疊
stloc.0 // 存入區域變數
// newobj 亦返回物件引用至堆疊
this 為第一參數 (ldarg.0)。ldarg 載入參數值,ldarga 載入參數位址 (ref/out)。
// C#: a.i = abc(); WriteLine(a.i);
// IL:
call int32 abc() // 返回值入棧
stfld int32 zzz::i // 存入實例欄位 (需 this 在棧下)
ldloc.0 (this); ldfld int32 zzz::i // 讀取欄位值入棧
call WriteLine(int32)
stfld 消耗堆疊頂值 + 物件引用。ldfld 載入物件引用 → 壓入欄位值 (非移除引用)。abc() 不接收 this,但呼叫端仍需保留 this 以便後續 stfld。box/unbox 橋接。this 隱式傳遞、建構函數鏈強制基類優先。.cctor 優於實例 .ctor。call (靜態/非虛) vs callvirt (虛/介面),參數左到右入棧 (文檔) / 右到左 (實際)。掌握本章內容,你已具備「讀懂 IL、理解 C# 底層行為、診斷疑難雜症」的核心能力。下章進入控制流 (選擇/迴圈) 與操作符映射篇。