一文詳解C#中方法重載的底層玩法
最近在看 C++ 的方法重載,我就在想 C# 中的重載底層是怎么玩的,很多朋友應(yīng)該知道 C 是不支持重載的,比如下面的代碼就會(huì)報(bào)錯(cuò)。
#include <stdio.h> int say() { return 1; } int say(int i) { return i; } int main() { say(10); return 0; }
從錯(cuò)誤信息看,它說 say
方法已經(jīng)存在了,尷尬。。。
一:為什么 C 不支持
要想尋找答案,需要了解一點(diǎn)點(diǎn)底層知識(shí),那就是編譯器在編譯 C 方法時(shí)會(huì)將函數(shù)名作為符號(hào)添加到符號(hào)表中,這個(gè)符號(hào)表 就是call到say方法字節(jié)碼中間的一個(gè)載體,畫個(gè)圖大概就是這樣。
簡(jiǎn)而言之,call 先跳轉(zhuǎn)到符號(hào)表, 然后再 jmp 到 say 方法,問題就出現(xiàn)在這里,符號(hào)表是一種類字典結(jié)構(gòu),是不可以出現(xiàn)符號(hào)相同的情況。對(duì)了,在 windbg 中我們可以用 x
命令去搜索這些符號(hào),
為了論證我的說法,可以在匯編層面給大家驗(yàn)證下,修改代碼如下:
#include <stdio.h> int say(int i) { return i; } int main() { say(10); return 0; }
接下來再看下匯編。
--------------- say(10) ----------- 00C41771 push 0Ah 00C41773 call _say (0C412ADh) --------------- 符號(hào)表 ----------- 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++ 是如何在符號(hào)表上實(shí)現(xiàn)唯一性突破。
二:C++ 符號(hào)表突破
為了方便講述,我們先上一段 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
有多個(gè),肯定是無法突破的,帶著好奇心我們看下它的反匯編代碼。
---------- 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)
從匯編代碼看, 調(diào)的都是 Person::sayhello
這個(gè)符號(hào),奇怪的是他們屬于不同的地址: 03B13A2h
, 03B1302h
,這就太奇怪了,哈哈,字典類符號(hào)表肯定是沒有問題的,問題是 Visual Studio 20222
的反匯編窗口在調(diào)試時(shí)做了一些內(nèi)部轉(zhuǎn)換,算是蒙蔽了我們雙眼吧,
真是可氣?。?!居然運(yùn)行時(shí)匯編代碼都還不夠徹底,那現(xiàn)在我們?cè)趺蠢^續(xù)挖呢? 可以用 IDA
去看這個(gè)程序的靜態(tài)反匯編代碼,截圖如下:
從代碼上的注釋可以清楚的看到,原來:
Person::sayhello(int)
變成了j_?sayhello@Person@@QAEXH@Z
。Person::sayhello(char const *)
變成了j_?sayhello@Person@@QAEXPBD@Z
到這里終于搞清楚了,原來 C++ 為了支持方法重載,將方法名做了重新編碼,這樣確實(shí)可以突破符號(hào)表的唯一性限制。
三:C#如何實(shí)現(xiàn)突破
我們都知道 C# 的底層 CLR 是由 C++ 寫的,所以大概率玩法都是一樣,接下來上一段代碼:
internal class Program { static void Main(string[] args) { //故意做一次重復(fù) 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
在運(yùn)行時(shí)動(dòng)態(tài)編譯的,并且首次編譯方法會(huì)先跳轉(zhuǎn)到 JIT 的樁地址,所以斷點(diǎn)必須下在第二次調(diào)用 Say(10)
處才能看到方法的符號(hào)地址,匯編代碼如下:
----------- 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
從輸出信息看,同樣也是兩個(gè)符號(hào)表地址,然后由符號(hào)表地址 jmp 到最后的方法體。
----------- Say(10); ----------- 00007FFB82134E01 call Method stub for: ConsoleApp1.Program.Say(Int32) (07FFB81F6F118h) ----------- 符號(hào)表 ----------- 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
暫時(shí)還不知道怎么看 JIT 改名后方法名,有知道的朋友可以留言一下哈,但總的來說還是 C++ 這一套。
以上就是一文詳解C#中方法重載的底層玩法 的詳細(xì)內(nèi)容,更多關(guān)于C#方法重載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#程序連接數(shù)據(jù)庫及讀取數(shù)據(jù)庫中字段的簡(jiǎn)單方法總結(jié)
包括C#連接Access、Oracle或者SQL Server,這里整理了一些C#連接數(shù)據(jù)庫及從讀取數(shù)據(jù)庫中字段的簡(jiǎn)單方法總結(jié),需要的朋友可以參考下2016-05-05C# Websocket連接實(shí)現(xiàn)wss協(xié)議
本文主要介紹了C# Websocket連接實(shí)現(xiàn)wss協(xié)議,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05C#影院售票系統(tǒng)畢業(yè)設(shè)計(jì)(4)
這篇文章主要介紹了C#影院售票系統(tǒng)畢業(yè)設(shè)計(jì),學(xué)習(xí)內(nèi)容是總結(jié)銷售信息的保存以及加載銷售信息,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-11-11C#用Parallel.Invoke方法盡可能并行執(zhí)行提供的每個(gè)線程
本文主要介紹了C#用Parallel.Invoke方法盡可能并行執(zhí)行提供的每個(gè)線程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01