.NET Emit 入門教程:第六部分:IL 指令:7:詳解 ILGenerator 指令方法:分支條件指令

来源:https://www.cnblogs.com/cyq1162/p/18130979
-Advertisement-
Play Games

經過前面幾篇的學習,我們瞭解到指令的大概分類,如:參數載入指令,該載入指令以 Ld 開頭,將參數載入到棧中,以便於後續執行操作命令。參數存儲指令,其指令以 St 開頭,將棧中的數據,存儲到指定的變數中,以方便後續使用。創建實例指令,其指令以 New 開頭,用於在運行時動態生成並初始化對象。方法調用指... ...


前言:

經過前面幾篇的學習,我們瞭解到指令的大概分類,如:

參數載入指令,該載入指令以 Ld 開頭,將參數載入到棧中,以便於後續執行操作命令。

參數存儲指令,其指令以 St 開頭,將棧中的數據,存儲到指定的變數中,以方便後續使用。

創建實例指令,其指令以 New 開頭,用於在運行時動態生成並初始化對象。

方法調用指令,該指令以 Call 開頭,用於在運行時調用其它方法。

本篇介紹分支條件指令,該指令通常以 Br、或 B、C 開頭,用於在運行分支條件時跳轉指令。

分支條件指令介紹:

分支條件指令是在.NET Emit編程中關鍵的控制流程工具,用於在IL代碼中實現條件判斷和控制轉移。

ILGenerator 類提供了一系列方法,用於生成這些分支條件指令,包括條件分支、無條件分支和Switch分支等。

條件分支指令(如brtrue和brfalse)根據棧頂的布爾值決定是否跳轉到目標標簽,而無條件分支指令(如br)則總是進行跳轉。

Switch分支指令則用於在多個目標中選擇一個跳轉。

通過比較指令(如ceq、cgt和clt),還可以進行數值比較並根據比較結果執行相應的跳轉操作。

這些指令的靈活運用可以實現複雜的控制邏輯,例如條件判斷、迴圈和異常處理等。

深入理解分支條件指令將幫助開發者更好地掌握.NET Emit編程,提高代碼生成的效率和靈活性。

常用分支條件指令:

條件跳轉指令:

  1. beq:如果兩個值相等,則跳轉到指定的標簽。
  2. bge:如果第一個值大於或等於第二個值,則跳轉到指定的標簽。
  3. bgt:如果第一個值大於第二個值,則跳轉到指定的標簽。
  4. ble:如果第一個值小於或等於第二個值,則跳轉到指定的標簽。
  5. blt:如果第一個值小於第二個值,則跳轉到指定的標簽.
  6. bne.un:如果兩個無符號整數值不相等,則跳轉到指定的標簽。
  7. brtrue:如果值為 true,則跳轉到指定的標簽。
  8. brfalse:如果值為 false,則跳轉到指定的標簽。
  9. brtrue.s:如果值為 true,則跳轉到指定的標簽(短格式)。
  10. brfalse.s:如果值為 false,則跳轉到指定的標簽(短格式).

無條件跳轉指令:

  1. br:無條件跳轉到指定的標簽。
  2. br.s:短格式的無條件跳轉到指定的標簽。
  3. leave:無條件跳轉到 try、filter 或 finally 塊的末尾。
  4. leave.s:短格式的無條件跳轉到 try、filter 或 finally 塊的末尾.

比較跳轉指令:

  1. bgt.un:如果第一個無符號整數值大於第二個值,則跳轉到指定的標簽。
  2. bge.un:如果第一個無符號整數值大於或等於第二個值,則跳轉到指定的標簽。
  3. blt.un:如果第一個無符號整數值小於第二個值,則跳轉到指定的標簽。
  4. ble.un:如果第一個無符號整數值小於或等於第二個值,則跳轉到指定的標簽.

其他跳轉指令:

  1. switch:根據給定的索引值跳轉到不同的標簽。
  2. brnull:如果值為 null,則跳轉到指定的標簽。
  3. brinst:如果對象是類的實例,則跳轉到指定的標簽。

這些指令可以幫助控制流程,在特定條件下跳轉到指定的標簽位置執行相應的代碼。

從以上分類說明可以看出,該指令需要配置標簽使用,對於標簽的用法,

如有遺忘,可以回去補一下文章:.NET Emit 入門教程:第六部分:IL 指令:2:詳解 ILGenerator 輔助方法

上面指令按使用方式,只分兩種:

1、條件跳轉指令:根據棧頂的數據,及指令的判斷條件,來跳轉標簽。

2、Switch 分支跳轉指令:根據給定的索引值,來跳轉標簽。

1、條件跳轉指令:

條件分支指令是在IL代碼中用於根據條件來執行跳轉操作的指令。

它們可以根據棧頂的(布爾)值來決定是否跳轉到目標標簽:

示例指令:Brtrue

示例指令:Brfalse

該 true、false 指令,除了 bool 值,還相容判斷了空(引用)和數字(零),這個小細節要註意。 

示例指令:Br

示例代碼:

var dynamicMethod = new DynamicMethod("WriteAOrB", typeof(void), new Type[] { typeof(bool) }, typeof(AssMethodIL_Condition));

ILGenerator il = dynamicMethod.GetILGenerator();

var labelEnd = il.DefineLabel();
var labelFalse = il.DefineLabel();

il.Emit(OpCodes.Ldarg_0);

il.Emit(OpCodes.Brfalse_S, labelFalse);

il.EmitWriteLine("true.");
il.Emit(OpCodes.Br_S, labelEnd);


il.MarkLabel(labelFalse);
il.EmitWriteLine("false");


il.MarkLabel(labelEnd);
il.Emit(OpCodes.Ret);     // 返回該值

運行結果:

說明:

1、在 truefalse 指令中,通常只會使用其中一個。 

2、在分支條件中,很多時候需要配合 Br 無條件指令跳出分支。

3、在定義標簽時,除了定義分支標簽,還要定義結束標簽,以便 Br 無條件指令的跳出。 

4、其它條件指令的使用,和bool條件指令的使用是一樣的,只需要理解指令的含義即可。

2、Switch 分支條件指令:

Switch 分支指令用於在多個目標中選擇一個跳轉,類似於在高級編程語言中的 switch 或者 case 語句。

在IL代碼中,Switch 指令可以實現根據一個整數值來決定跳轉到不同的目標標簽。Switch 分支指令的主要指令是 switch,其作用如下:

  • switch: 該指令從標簽數組中選擇一個目標標簽進行跳轉。在 IL 代碼中,標簽數組通常在 switch 指令之前被定義,並且 switch 指令的操作數是標簽數組的引用。switch 指令會從操作數指定的標簽數組中根據整數值索引來選擇目標標簽,然後執行跳轉操作。

Switch 分支指令的作用是根據一個整數值來決定跳轉到不同的目標標簽,這在處理具有多個選擇的情況下非常有用,可以使得代碼更加簡潔和高效。

註意事項:

1、該 Switch 指令只是類似 switch 編程,但不等同。 

2、該 Switch 指令只能根據索引進行指令跳轉。

同時,Switch 指令在IL代碼編寫起來,會相對複雜一點,特別是 case 一多,寫起來可會要你命3000.

為了區分 Switch 指令和我們編寫代碼時的指令 Switch case 區別,我們來看以下示例:

這一次我們反過來,先寫 C# 代碼,再看它生成的 IL 代碼。

下麵給一個示例代碼1:

static void TestString(string c)
{
    switch(c)
    {
        case "a":
            Console.WriteLine("A");break;
        case "b":
            Console.WriteLine("B");break;
        default:
            Console.WriteLine("C");break;
    }
}

反編繹,看生成的 IL 代碼:

.method private hidebysig static 
        void TestString (
            string c
        ) cil managed 
    {
        // Method begins at RVA 0x3808
        // Code size 73 (0x49)
        .maxstack 2
        .locals init (
            [0] string,
            [1] string
        )

        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: stloc.1
        IL_0003: ldloc.1
        IL_0004: stloc.0
        IL_0005: ldloc.0
        IL_0006: ldstr "a"
        IL_000b: call bool [mscorlib]System.String::op_Equality(string, string)
        IL_0010: brtrue.s IL_0021

        IL_0012: ldloc.0
        IL_0013: ldstr "b"
        IL_0018: call bool [mscorlib]System.String::op_Equality(string, string)
        IL_001d: brtrue.s IL_002e

        IL_001f: br.s IL_003b

        IL_0021: ldstr "A"
        IL_0026: call void [mscorlib]System.Console::WriteLine(string)
        IL_002b: nop
        IL_002c: br.s IL_0048

        IL_002e: ldstr "B"
        IL_0033: call void [mscorlib]System.Console::WriteLine(string)
        IL_0038: nop
        IL_0039: br.s IL_0048

        IL_003b: ldstr "C"
        IL_0040: call void [mscorlib]System.Console::WriteLine(string)
        IL_0045: nop
        IL_0046: br.s IL_0048

        IL_0048: ret
    } // end of method Program::TestString

從該生成的 IL 代碼中,可以看出並沒有 Switch 指令,而是常規調用字元串比較後,用 bool 條件指令進行跳轉。

那是不是用數字類型就會得到 Switch 指令呢?

再用數字型來一次:

        static void TestInt(int c)
        {
            switch (c)
            {
                case 1:
                    Console.WriteLine("AAA"); break;
                case 2:
                    Console.WriteLine("BBB"); break;
                default:
                    Console.WriteLine("CCC"); break;
            }
        }

得到的 IL 代碼如下:

.method private hidebysig static 
        void TestInt (
            int32 c
        ) cil managed 
    {
        // Method begins at RVA 0x3860
        // Code size 57 (0x39)
        .maxstack 2
        .locals init (
            [0] int32,
            [1] int32
        )

        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: stloc.1
        IL_0003: ldloc.1
        IL_0004: stloc.0
        IL_0005: ldloc.0
        IL_0006: ldc.i4.1
        IL_0007: beq.s IL_0011

        IL_0009: br.s IL_000b

        IL_000b: ldloc.0
        IL_000c: ldc.i4.2
        IL_000d: beq.s IL_001e

        IL_000f: br.s IL_002b

        IL_0011: ldstr "AAA"
        IL_0016: call void [mscorlib]System.Console::WriteLine(string)
        IL_001b: nop
        IL_001c: br.s IL_0038

        IL_001e: ldstr "BBB"
        IL_0023: call void [mscorlib]System.Console::WriteLine(string)
        IL_0028: nop
        IL_0029: br.s IL_0038

        IL_002b: ldstr "CCC"
        IL_0030: call void [mscorlib]System.Console::WriteLine(string)
        IL_0035: nop
        IL_0036: br.s IL_0038

        IL_0038: ret
    } // end of method Program::TestInt

依舊是 br 跳轉指令。

再試一下用枚舉呢?

        static void TestEnum(DataAccessKind c)
        {
            switch (c)
            {
                case  DataAccessKind.Read:
                    Console.WriteLine("AAA"); break;
                case DataAccessKind.None:
                    Console.WriteLine("BBB"); break;
                default:
                    Console.WriteLine("CCC"); break;
            }
        }

得到 IL 代碼如下:

.method private hidebysig static 
        void TestEnum (
            valuetype [System.Data]Microsoft.SqlServer.Server.DataAccessKind c
        ) cil managed 
    {
        // Method begins at RVA 0x38a8
        // Code size 56 (0x38)
        .maxstack 2
        .locals init (
            [0] valuetype [System.Data]Microsoft.SqlServer.Server.DataAccessKind,
            [1] valuetype [System.Data]Microsoft.SqlServer.Server.DataAccessKind
        )

        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: stloc.1
        IL_0003: ldloc.1
        IL_0004: stloc.0
        IL_0005: ldloc.0
        IL_0006: brfalse.s IL_001d

        IL_0008: br.s IL_000a

        IL_000a: ldloc.0
        IL_000b: ldc.i4.1
        IL_000c: beq.s IL_0010

        IL_000e: br.s IL_002a

        IL_0010: ldstr "AAA"
        IL_0015: call void [mscorlib]System.Console::WriteLine(string)
        IL_001a: nop
        IL_001b: br.s IL_0037

        IL_001d: ldstr "BBB"
        IL_0022: call void [mscorlib]System.Console::WriteLine(string)
        IL_0027: nop
        IL_0028: br.s IL_0037

        IL_002a: ldstr "CCC"
        IL_002f: call void [mscorlib]System.Console::WriteLine(string)
        IL_0034: nop
        IL_0035: br.s IL_0037

        IL_0037: ret
    } // end of method Program::TestEnum

還是沒有見 Switch 指令。

可見,編程中的Switch,和 IL 的 Switch 指令是不同的。

所以很容易陷入一個誤區,以為代碼用 Switch 寫分支,對應的IL就得用 Switch 指令。

下麵演示一個用 Switch 指令的示例:

 var dynamicMethod = new DynamicMethod("WriteAOrB", typeof(void), new Type[] { typeof(int) }, typeof(AssMethodIL_Condition));

 ILGenerator il = dynamicMethod.GetILGenerator();

 var labelEnd = il.DefineLabel();

 Label labelIndex_0 = il.DefineLabel();
 Label labelIndex_1 = il.DefineLabel();
 Label labelIndex_2 = il.DefineLabel();
 //BreakOp None=-1,Null=0,Empty=1,NullOrEmpty=2
 var lables = new Label[] { labelIndex_0, labelIndex_1, labelIndex_2 };//0、1、2

 il.Emit(OpCodes.Ldarg_0);

 il.Emit(OpCodes.Switch, lables);

 il.MarkLabel(labelIndex_0);
 il.EmitWriteLine("0.");
 il.Emit(OpCodes.Br_S, labelEnd);

 il.MarkLabel(labelIndex_1);
 il.EmitWriteLine("1.");
 il.Emit(OpCodes.Br_S, labelEnd);

 il.MarkLabel(labelIndex_2);
 il.EmitWriteLine("2.");
 il.Emit(OpCodes.Br_S, labelEnd);

 il.MarkLabel(labelEnd);
 il.Emit(OpCodes.Ret);     // 返回該值

 dynamicMethod.Invoke(null, new object[] { 1 });


 Console.Read();

運行結果:

從上面的示例可以看出,使用 Switch 指令,其實是在 IL 中編寫類似於 Switch 語法的條件分支,而不是和C# 語法的 Switch 對應。

總結:

本篇介紹了在IL(Intermediate Language)代碼中常見的兩種指令類型:條件跳轉指令和Switch 分支跳轉指令。

條件跳轉指令則用於執行數值比較操作,根據比較結果執行相應的跳轉操作或將比較結果壓入棧中。

由於其使用方式是一致,因此示例僅展示bool條件指令的使用,沒有對其它指令展開示例,但其它條件指令的含義,也是需要仔細瞭解一下的。

Switch 分支跳轉指令用於根據一個整數值選擇不同的目標標簽進行跳轉,類似於高級編程語言中的 switch 或者 case 語句。

通過 Switch 指令,可以使代碼更加簡潔、高效,並提高可讀性和可維護性。在處理具有多個選擇的情況下特別有用,例如枚舉類型或者狀態機的處理。

它們在條件分支指令和迴圈控制中起著關鍵作用,通過靈活運用比較指令,可以實現各種複雜的演算法和邏輯。

綜上所述,Switch 分支跳轉指令和條件跳轉指令是IL代碼中常用的兩種控制流指令,它們在編寫和優化IL代碼時起著重要作用,能夠使代碼更加簡潔、高效和易於理解。

版權聲明:本文原創發表於 博客園,作者為 路過秋天 本文歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則視為侵權。
個人微信公眾號
Donation(掃碼支持作者):支付寶:
Donation(掃碼支持作者):微信:

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • decltype關鍵字是C++11新標準引入的關鍵字,它和關鍵字auto的功能類似,也可以自動推導出給定表達式的類型,但它和auto的語法有些不同,這篇文章講解了decltype的使用場景以及和auto不同的地方,同時也講解了和auto結合使用的用法。 ...
  • 為了增加查詢的性能,MyBatis 提供了二級緩存架構,分為一級緩存和二級緩存。 這兩級緩存最大的區別就是:一級緩存是會話級別的,只要出了這個 SqlSession,緩存就沒用了。而二級緩存可以跨會話,多個會話可以使用相同的緩存! 一級緩存使用簡單,預設就開啟。二級緩存需要手動開啟,相對複雜,而且要 ...
  • 本文深入探討了Kubernetes Pod配置的實戰技巧和常見易錯點。 關註【TechLeadCloud】,分享互聯網架構、雲服務技術的全維度知識。作者擁有10+年互聯網服務架構、AI產品研發經驗、團隊管理經驗,同濟本復旦碩,復旦機器人智能實驗室成員,阿裡雲認證的資深架構師,項目管理專業人士,上億營 ...
  • 本文分享自華為雲社區《Python 正則表達式大揭秘應用與技巧全解析》,作者:檸檬味擁抱。 Python 中的 re 模塊是用於處理正則表達式的強大工具。正則表達式是一種用來匹配字元串的模式,它可以在文本中搜索和匹配特定的字元串模式。在本文中,我們將探討 Python 中 re 模塊的應用和一些技巧 ...
  • 寫在前面 一款好的插件往往能提高我們的開發效率。今天就給大家安利一款maven 依賴搜索插件。 插件是自己一直關註的魯班大叔開發的,用了幾天真的好用 廢話不多說,我們就來看看這是一款什麼插件 一、maven 依賴搜索 平常我們需要找一個maven依賴,一般都會去 https://mvnreposit ...
  • OOM是什麼?英文全稱為 OutOfMemoryError(記憶體溢出錯誤)。當程式發生OOM時,如何去定位導致異常的代碼還是挺麻煩的。 要檢查OOM發生的原因,首先需要瞭解各種OOM情況下會報的異常信息。這樣能縮小排查範圍,再結合異常堆棧、heapDump文件、JVM分析工具和業務代碼來判斷具體是哪 ...
  • 1 開源解析和拆分文檔 第三方的工具去對文件解析拆分,去將我們的文件內容給提取出來,並將我們的文檔內容去拆分成一個小的chunk。常見的PDF word mark down, JSON、HTML。都可以有很好的一些模塊去把這些文件去進行一個東西去提取。 優勢 支持豐富的文檔類型 每種文檔多樣化選擇 ...
  • LiteDB 是一個輕量級的嵌入式 NoSQL 資料庫,其設計理念與 MongoDB 類似,但它是完全使用 C# 開發的,因此與 C# 應用程式的集成非常順暢。與 SQLite 相比,LiteDB 提供了 NoSQL(即鍵值對)的數據存儲方式,並且是一個開源且免費的項目。它適用於桌面、移動以及 We ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...