聊聊 C# 方法重載的底層玩法

来源:https://www.cnblogs.com/huangxincheng/archive/2022/06/15/16378081.html
-Advertisement-
Play Games

最近在看 C++ 的方法重載,我就在想 C# 中的重載底層是怎麼玩的,很多朋友應該知道 C 是不支持重載的,比如下麵的代碼就會報錯。 #include <stdio.h> int say() { return 1; } int say(int i) { return i; } int main() ...


最近在看 C++ 的方法重載,我就在想 C# 中的重載底層是怎麼玩的,很多朋友應該知道 C 是不支持重載的,比如下麵的代碼就會報錯。


#include <stdio.h>

int say() {
	return 1;
}
int say(int i) {
	return i;
}

int main()
{
	say(10);
	return 0;
}

從錯誤信息看,它說 say 方法已經存在了,尷尬。。。

一:為什麼 C 不支持

要想尋找答案,需要瞭解一點點底層知識,那就是編譯器在編譯 C 方法時會將 函數名 作為符號添加到 符號表 中,這個 符號表 就是 call 到 say方法位元組碼 中間的一個載體,畫個圖大概就是這樣。

簡而言之,call 先跳轉到 符號表, 然後再 jmp 到 say 方法,問題就出現在這裡,符號表是一種類字典結構,是不可以出現 符號 相同的情況。對了,在 windbg 中我們可以用 x 命令去搜索這些符號,

為了論證我的說法,可以在彙編層面給大家驗證下,修改代碼如下:


#include <stdio.h>

int say(int i) {
	return i;
}

int main()
{
	say(10);
	return 0;
}

接下來再看下彙編。


--------------- say(10) -----------

00C41771  push        0Ah  
00C41773  call        _say (0C412ADh)  

--------------- 符號表 -----------

00C412AD  jmp         say (0C417B0h)  

--------------- say body -----------

00C417B0  push        ebp  
00C417B1  mov         ebp,esp  
00C417B3  sub         esp,0C0h  
00C417B9  push        ebx  
00C417BA  push        esi  
00C417BB  push        edi  
00C417BC  mov         edi,ebp  
00C417BE  xor         ecx,ecx  
00C417C0  mov         eax,0CCCCCCCCh  
00C417C5  rep stos    dword ptr es:[edi]  
00C417C7  mov         ecx,offset _2440747F_ConsoleApplication6@c (0C4C008h)  
...

知道了原理後,我們再看看 C++ 是如何在 符號表 上實現唯一性突破。

二:C++ 符號表突破

為了方便講述,我們先上一段 C++ 方法重載的代碼。


using namespace std;

class Person
{
public:
	void sayhello(int i) {
		cout << i << endl;
	}
	void sayhello(const char* c) {
		cout << c << endl;
	}
};

int main(int argc)
{
	Person person;

	person.sayhello(10);
	person.sayhello("hello world");
}

按理說 sayhello 有多個,肯定是無法突破的,帶著好奇心我們看下它的反彙編代碼。


----------     person.sayhello(10);  ----------------

003B2E5F  push        0Ah  
003B2E61  lea         ecx,[person]  
003B2E64  call        Person::sayhello (03B13A2h) 

------------  person.sayhello("hello world"); ----------------

003B2E69  push        offset string "hello world" (03B9C2Ch)  
003B2E6E  lea         ecx,[person]  
003B2E71  call        Person::sayhello (03B1302h) 

從彙編代碼看, 調的都是 Person::sayhello 這個符號,奇怪的是他們屬於不同的地址: 03B13A2h, 03B1302h,這就太奇怪了,哈哈,字典類符號表 肯定是沒有問題的,問題是 Visual Studio 20222 的反彙編視窗在調試時做了一些內部轉換,算是矇蔽了我們雙眼吧,

真是可氣!!!居然運行時彙編代碼都還不夠徹底,那現在我們怎麼繼續挖呢? 可以用 IDA 去看這個程式的 靜態反彙編代碼,截圖如下:

從代碼上的註釋可以清楚的看到,原來:

  1. Person::sayhello(int) 變成了 j_?sayhello@Person@@QAEXH@Z
  2. Person::sayhello(char const *) 變成了 j_?sayhello@Person@@QAEXPBD@Z

到這裡終於搞清楚了,原來 C++ 為了支持方法重載,將 方法名 做了重新編碼,這樣確實可以突破 符號表 的唯一性限制。

三:C# 如何實現突破

我們都知道 C# 的底層 CLR 是由 C++ 寫的,所以大概率玩法都是一樣,接下來上一段代碼:


    internal class Program
    {
        static void Main(string[] args)
        {
			//故意做一次重覆
            Say(10);
            Say("hello world");

            Say(10);
            Say("hello world");
            Console.ReadLine();
        }

        static void Say(int i)
        {
            Console.WriteLine(i);
        }

        static void Say(string s)
        {
            Console.WriteLine(s);
        }
    }

由於 C# 的方法是由 JIT 在運行時動態編譯的,並且首次編譯方法會先跳轉到 JIT 的樁地址,所以斷點必須下在第二次調用 Say(10) 處才能看到方法的符號地址,彙編代碼如下:

 -----------	Say(10);	-----------

00007FFB82134DFC  mov         ecx,0Ah  
00007FFB82134E01  call        Method stub for: ConsoleApp1.Program.Say(Int32) (07FFB81F6F118h)  
00007FFB82134E06  nop  

-----------	Say("hello world");		-----------

00007FFB82134E07  mov         rcx,qword ptr [1A8C65E8h]  
00007FFB82134E0F  call        Method stub for: ConsoleApp1.Program.Say(System.String) (07FFB81F6F120h)  
00007FFB82134E14  nop  

從輸出信息看,同樣也是兩個符號表地址,然後由符號表地址 jmp 到最後的方法體。


-----------	Say(10);	-----------
00007FFB82134E01  call        Method stub for: ConsoleApp1.Program.Say(Int32) (07FFB81F6F118h)  

-----------	符號表	-----------
00007FFB81F6F118  jmp         ConsoleApp1.Program.Say(Int32) (07FFB82134F10h)  

-----------	Say body -----------

00007FFB82134F10  push        rbp  
00007FFB82134F11  push        rdi  
00007FFB82134F12  push        rsi  
00007FFB82134F13  sub         rsp,20h  
00007FFB82134F17  mov         rbp,rsp  
00007FFB82134F1A  mov         dword ptr [rbp+40h],ecx  
00007FFB82134F1D  cmp         dword ptr [7FFB82036B80h],0  
00007FFB82134F24  je          ConsoleApp1.Program.Say(Int32)+01Bh (07FFB82134F2Bh)  
00007FFB82134F26  call        00007FFBE1C2CC40  

暫時還不知道怎麼看 JIT 改名後 方法名,有知道的朋友可以留言一下哈,但總的來說還是 C++ 這一套。

好了本篇就聊到這裡,希望對你有幫助。

圖片名稱
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 作者:永動的圖靈機 鏈接:https://juejin.cn/post/6844904033488994317 統一結果返回 目前的前後端開發大部分數據的傳輸格式都是json,因此定義一個統一規範的數據格式有利於前後端的交互與UI的展示。 統一結果的一般形式 是否響應成功; 響應狀態碼; 狀態碼描述 ...
  • 今天產品下了新需求,要給系統加雙語切換,在做到國家城市庫映射的時候,我在度娘上找了個雙語版的資料庫,可是系統已經在生產半年了,直接換表肯定是要背鍋的,轉念一想,要是能把城市信息直接替換為拼音不就行了,在此將操作步驟整理下來。 首先需要安裝一下擴展overtrue/pinyin,執行命令,我的框架是6 ...
  • 函數中參數傳值 1、基本數據類型傳值 當函數中傳入的參數為基本數據類型時,函數中對傳入參數的操作不會對函數外的數據產生影響。由於基本數據類型的變數名指向的是具體的數值,在函數內部,相當於將參數進行了拷貝,函數內只對拷貝後的參數進行操作。 基本數據類型 public class ParamsTrans ...
  • 1.1.1 JAVA概述 Java版本 原網址(https://www.oracle.com/java/technologies/java-se-support-roadmap.html) Oracle 將僅將某些版本指定為長期支持 (LTS) 版本。Java SE 7、8、11 和 17 是 LT ...
  • 本項目將使用python3去識別圖片是否為色情圖片,會使用到PIL這個圖像處理庫,並且編寫演算法來劃分圖像的皮膚區域 介紹一下PIL: PIL(Python Image Library)是一種免費的圖像處理工具包,這個軟體包提供了基本的圖像處理功能,如:改變圖像大小,旋轉 圖像,圖像格式轉化,色場空間 ...
  • Android Jetpack Navigation基本使用 本篇主要介紹一下 Android Jetpack 組件 Navigation 導航組件的 基本使用 當看到 Navigation單詞的時候 應該就大概知道 這是一個關於導航用的,下麵我來簡單介紹一下 如何使用Navigation組件的基本 ...
  • APB匯流排信號: APB匯流排狀態機與讀寫Timing IDIE是初始化態; SETUP是從機被PSELx選中以後進入的狀態,只維持一個cycle,下一個周期的上升沿到ENABLE態; ENABLE要使PENABLE HIGH,同時如果沒有繼續transfer那麼從ENABLE跳到IDIE,如果有繼續 ...
  • 本文以C#及VB.NET後端程式代碼示例展示如何將HTML轉為XML文件。轉換時,調用Word API -Free Spire.Doc for .NET 提供的文檔載入方法及文檔保存的方法來實現。轉換的代碼步驟很簡單,具體可參考以下內容。 引入dll 1.通過NuGet安裝dll(2種方法) 1.1 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...