聊聊 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
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...