聊聊 C# 中的多態底層 (虛方法調用) 是怎么玩的

最近在看 C++ 的虛方法調用實現原理,大概就是說在 class 的首位置存放著一個指向 vtable array 指針數組 的指針,而 vtable array 中的每一個指針元素指向的就是各自的 虛方法,實現方式很有意思,哈哈,現在我很好奇 C# 中如何實現的。

一:C# 中的多態玩法

1. 一個簡單的 C# 例子

為了方便說明,我就定義一個 Person 類和一個 Chinese 類,詳細代碼如下:

internal?class?Program{static?void?Main(string[]?args){Person?person?=?new?Chinese();person.SayHello();Console.ReadLine();}}public?class?Person{public?virtual?void?SayHello(){Console.WriteLine("sayhello");}}public?class?Chinese:?Person{public?override?void?SayHello(){Console.WriteLine("chinese");}}
}

2. 匯編代碼分析

接下來用 windbg 在 person.SayHello() 處下一個斷點,觀察一下它的反匯編代碼:

D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs?@?9:
05cf21b3?b93c5dce05??????mov?????ecx,5CE5D3Ch?(MT:?ConsoleApp1.Chinese)
05cf21b8?e8030f89fa??????call????005830c0?(JitHelp:?CORINFO_HELP_NEWSFAST)
05cf21bd?8945f4??????????mov?????dword?ptr?[ebp-0Ch],eax
05cf21c0?8b4df4??????????mov?????ecx,dword?ptr?[ebp-0Ch]
05cf21c3?e820fbffff??????call????05cf1ce8?(ConsoleApp1.Chinese..ctor(),?mdToken:?0600000A)
05cf21c8?8b4df4??????????mov?????ecx,dword?ptr?[ebp-0Ch]
05cf21cb?894df8??????????mov?????dword?ptr?[ebp-8],ecxD:\net6\ConsoleApplication2\ConsoleApp1\Program.cs?@?11:
>>>?05cf21ce?8b4df8??????????mov?????ecx,dword?ptr?[ebp-8]
05cf21d1?8b45f8??????????mov?????eax,dword?ptr?[ebp-8]
05cf21d4?8b00????????????mov?????eax,dword?ptr?[eax]
05cf21d6?8b4028??????????mov?????eax,dword?ptr?[eax+28h]
05cf21d9?ff5010??????????call????dword?ptr?[eax+10h]
05cf21dc?90??????????????nop

從匯編代碼看,邏輯非常清晰,大體步驟如下:

  1. eax,dword ptr [ebp-8]

從棧上(ebp-8)處獲取 person 在堆上的首地址,如果不相信的話,可以用 !do 027ea88c 試試看。

0:000>?dp?ebp-8?L1
0057f300??027ea88c0:000>?!do?027ea88c
Name:????????ConsoleApp1.Chinese
MethodTable:?05ce5d3c
EEClass:?????05cd3380
Size:????????12(0xc)?bytes
File:????????D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dll
Fields:
None
  1. eax,dword ptr [eax]

如果大家了解 實例 在堆上的內存布局的話,應該知道,這個首地址存放的就是 methodtable 指針,我們可以用 !dumpmt 05ce5d3c 來驗證下。

0:000>?dp?027ea88c?L1
027ea88c??05ce5d3c0:000>?!dumpmt?05ce5d3c
EEClass:?????????05cd3380
Module:??????????05addb14
Name:????????????ConsoleApp1.Chinese
mdToken:?????????02000007
File:????????????D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dll
BaseSize:????????0xc
ComponentSize:???0x0
DynamicStatics:??false
ContainsPointers?false
Slots?in?VTable:?6
Number?of?IFaces?in?IFaceMap:?0
  1. eax,dword ptr [eax+28h]

那這句話是什么意思呢?如果你了解 CoreCLR 的話,你應該知道 methedtable 是由一個 class MethodTable 類來承載的,所以它取了 methodtable 偏移 0x28 ?位置的一個字段,那這個偏移字段是什么呢?我們先用 dt 把 methodtable 結構給導出來。

0:000>?dt?05ce5d3c?MethodTable
coreclr!MethodTable=7ad96bc8?s_pMethodDataCache?:?0x00639ec8?MethodDataCache=7ad96bc4?s_fUseParentMethodData?:?0n1=7ad96bcc?s_fUseMethodDataCache?:?0n1+0x000?m_dwFlags????????:?0xc+0x004?m_BaseSize???????:?0x74088+0x008?m_wFlags2????????:?5+0x00a?m_wToken?????????:?0+0x00c?m_wNumVirtuals???:?0x5ccc+0x00e?m_wNumInterfaces?:?0x5ce+0x010?m_pParentMethodTable?:?IndirectPointer<MethodTable?*>+0x014?m_pLoaderModule??:?PlainPointer<Module?*>+0x018?m_pWriteableData?:?PlainPointer<MethodTableWriteableData?*>+0x01c?m_pEEClass???????:?PlainPointer<EEClass?*>+0x01c?m_pCanonMT???????:?PlainPointer<unsigned?long>+0x020?m_pPerInstInfo???:?PlainPointer<PlainPointer<Dictionary?*>?*>+0x020?m_ElementTypeHnd?:?0+0x020?m_pMultipurposeSlot1?:?0+0x024?m_pInterfaceMap??:?PlainPointer<InterfaceInfo_t?*>+0x024?m_pMultipurposeSlot2?:?0x5ce5d68=7ad04c78?c_DispatchMapSlotOffsets?:?[0]??"?$?(System.Private.CoreLib.dll"=7ad04c70?c_NonVirtualSlotsOffsets?:?[0]??"?$?($((,?$?(System.Private.CoreLib.dll"=7ad04c60?c_ModuleOverrideOffsets?:?[0]??"?$?($((,$((,(,,0?$?($((,?$?(System.Private.CoreLib.dll"=7ad12838?c_OptionalMembersStartOffsets?:?[0]??"(((((((,(((,(,,0(((,(,,0(,,0,004"

從 methodtable 的布局圖來看, eax+28hm_pMultipurposeSlot2 結構的第二個字段了,因為第一個字段是 虛方法表指針,如果要驗證的話,也很簡單,用 !dumpmt -md 05ce5d3c 把所有的方法給導出來,然后結合 dp 05ce5d3c 看下 0x5ce5d68 之后是不是許多的方法。

0:000>?!dumpmt?-md?05ce5d3c
EEClass:?????????05cd3380
Module:??????????05addb14
Name:????????????ConsoleApp1.Chinese
mdToken:?????????02000007
File:????????????D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dll
BaseSize:????????0xc
ComponentSize:???0x0
DynamicStatics:??false
ContainsPointers?false
Slots?in?VTable:?6
Number?of?IFaces?in?IFaceMap:?0
--------------------------------------
MethodDesc?TableEntry?MethodDe????JIT?Name
02610028?02605568???NONE?System.Object.Finalize()
02610030?02605574???NONE?System.Object.ToString()
02610038?02605580???NONE?System.Object.Equals(System.Object)
02610050?026055ac???NONE?System.Object.GetHashCode()
05CF1CE0?05ce5d24???NONE?ConsoleApp1.Chinese.SayHello()
05CF1CE8?05ce5d30????JIT?ConsoleApp1.Chinese..ctor()
0:000>?dp?05ce5d3c?L10
05ce5d3c??00000200?0000000c?00074088?00000005
05ce5d4c??05ce5ccc?05addb14?05ce5d7c?05cd3380
05ce5d5c??05cf1ce8?00000000?05ce5d68?02610028
05ce5d6c??02610030?02610038?02610050?05cf1ce0

仔細看輸出,上面的 05ce5d68 后面的 02610028 就是 System.Object.Finalize() 方法,02610030 對應著 System.Object.ToString() 方法。

  1. call dword ptr [eax+10h]

有了前面的基礎,這句話就好理解了,它是從 m_pMultipurposeSlot2 結構中找 SayHello 所在的單元指針位置,然后做 call 調用。

0:000>?!U?05cf1ce0
Unmanaged?code
05cf1ce0?e88f9dde74??????call????coreclr!PrecodeFixupThunk?(7aadba74)
05cf1ce5?5e??????????????pop?????esi
05cf1ce6?0001????????????add?????byte?ptr?[ecx],al
05cf1ce8?e913050000??????jmp?????05cf2200
05cf1ced?5f??????????????pop?????edi
05cf1cee?0300????????????add?????eax,dword?ptr?[eax]
05cf1cf0?245d????????????and?????al,5Dh
05cf1cf2?ce??????????????into
05cf1cf3?0500000000??????add?????eax,0
05cf1cf8?0000????????????add?????byte?ptr?[eax],al

從匯編看,它還是一段 樁代碼,言外之意就是該方法沒有被 JIT 編譯,如果編譯完了,這里的 05CF1CE0 05ce5d24 NONE ConsoleApp1.Chinese.SayHello() ?的 Entry (05CF1CE0) 也會被同步修改,驗證一下很簡單,我們繼續 go 代碼讓其編譯完成,然后再 dumpmt 。

0:008>?!dumpmt?-md?05ce5d3c
EEClass:?????????05cd3380
Module:??????????05addb14
Name:????????????ConsoleApp1.Chinese
mdToken:?????????02000007
File:????????????D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dll
BaseSize:????????0xc
ComponentSize:???0x0
DynamicStatics:??false
ContainsPointers?false
Slots?in?VTable:?6
Number?of?IFaces?in?IFaceMap:?0
--------------------------------------
MethodDesc?TableEntry?MethodDe????JIT?Name
02610028?02605568???NONE?System.Object.Finalize()
02610030?02605574???NONE?System.Object.ToString()
02610038?02605580???NONE?System.Object.Equals(System.Object)
02610050?026055ac???NONE?System.Object.GetHashCode()
05CF2270?05ce5d24????JIT?ConsoleApp1.Chinese.SayHello()
05CF1CE8?05ce5d30????JIT?ConsoleApp1.Chinese..ctor()0:008>?dp?05ce5d3c?L10
05ce5d3c??00000200?0000000c?00074088?00000005
05ce5d4c??05ce5ccc?05addb14?05ce5d7c?05cd3380
05ce5d5c??05cf1ce8?00000000?05ce5d68?02610028
05ce5d6c??02610030?02610038?02610050?05cf2270

此時可以看到它由 05cf1ce0 變成了 05cf2270, 這個就是 JIT 編譯后的方法代碼,我們用 !U 反編譯下。

0:008>?!U?05cf2270
Normal?JIT?generated?code
ConsoleApp1.Chinese.SayHello()
ilAddr?is?05E720D5?pImport?is?008F6E88
Begin?05CF2270,?size?27D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs?@?28:
>>>?05cf2270?55??????????????push????ebp
05cf2271?8bec????????????mov?????ebp,esp
05cf2273?50??????????????push????eax
05cf2274?894dfc??????????mov?????dword?ptr?[ebp-4],ecx
05cf2277?833d74dcad0500??cmp?????dword?ptr?ds:[5ADDC74h],0
05cf227e?7405????????????je??????05cf2285
05cf2280?e8cb2bf174??????call????coreclr!JIT_DbgIsJustMyCode?(7ac04e50)
05cf2285?90??????????????nopD:\net6\ConsoleApplication2\ConsoleApp1\Program.cs?@?29:
05cf2286?8b0d74207e04????mov?????ecx,dword?ptr?ds:[47E2074h]?("chinese")
05cf228c?e8dffbffff??????call????05cf1e70
05cf2291?90??????????????nopD:\net6\ConsoleApplication2\ConsoleApp1\Program.cs?@?30:
05cf2292?90??????????????nop
05cf2293?8be5????????????mov?????esp,ebp
05cf2295?5d??????????????pop?????ebp
05cf2296?c3??????????????ret

終于這就是多態下的 ConsoleApp1.Chinese.SayHello 方法啦。

3. 總結

本質上來說,CoreCLR 也是 C++ 寫的,所以也逃不過用 虛表 來實現多態的玩法, 不過玩法也稍微復雜了一些,希望本篇對大家有幫助。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/286700.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/286700.shtml
英文地址,請注明出處:http://en.pswp.cn/news/286700.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

sublime text 3 安裝、添加命令行啟動、漢化、注冊碼

1. 安裝sublime&#xff1a; 下載&#xff1a;http://www.sublimetext.com/3 添加命令行啟動&#xff1a;設置環境變量->計算機->右鍵屬性->高級系統設置->環境變量->系統變量->Path->編輯&#xff0c;加入";安裝路徑"(如&#xff1a;;D:\Progr…

MVC基礎知識-View

public ActionResult Index() {ViewBag.Message "Modify this template to jump-start your ASP.NET MVC application.";//展現到視圖中數據//~/Views/Home/Index.cshtmlreturn View(); //展現指定的視圖&#xff0c;當沒有指定視圖名稱時&#xff0c;默認是指向根目…

Android之設置當前app為默認瀏覽器研究

1 需求 把當前app設置為默認瀏覽器 2 主要思路 分析競品,我們主要通過2種方式設置app為默認瀏覽器 直接跳 手機“設置默認應用”界面來讓用戶選擇 默認瀏覽器為哪個app打開一個鏈接,拉起手機所有瀏覽器,讓用戶去選擇哪個瀏覽器,并且只有點擊“始終”才會生效,僅僅點擊“…

Linux下的用戶和組

2019獨角獸企業重金招聘Python工程師標準>>> 用戶和組 GNU/Linux 通過用戶和用戶組實現訪問控制 —— 包括對文件訪問、設備使用的控制。Linux 默認的訪問控制機制相對簡單直接&#xff0c;不過還有一些更加高級的機制&#xff0c;包括 ACL 和 LDAP Authentication.…

漫畫C語言 做個聊天軟件你不懂也得懂

學完C語言做不出東西&#xff1f;不存在的&#xff0c;咱們做一個最“隱私”的聊天器&#xff0c;就倆人&#xff0c;你和我。咱們聊天的信息你知我知沒別人知。 對了&#xff0c;本文評論區點贊、收藏抽獎。 社區也有抽獎&#xff0c;本周社區抽獎帖子 &#xff1a;https://b…

【Microstation】第一章:Microstation三維模型構建概述

MicroStation 是國際上和AutoCAD齊名的二維和三維CAD設計軟件&#xff0c;第一個版本由Bentley兄弟在1986年開發完成。其專用格式是DGN&#xff0c;并兼容AutoCAD的DWG/DXF等格式。 MicroStation是Bentley 工程軟件系統有限公司在建筑、土木工程、交通運輸、加工工廠、離散制造…

libgdx游戲引擎開發筆記(十)SuperJumper游戲例子的講解(篇四)---- 主游戲界面內部框架編寫...

上一講&#xff0c;我們已經實現了點擊play進入游戲界面但僅僅是個黑屏 今天&#xff0c;我們就試著編寫代碼讓它出現游戲的一些簡單場景。還是在上一講的代碼基礎上&#xff0c;我們創建兩個類&#xff1a;World 和 WorldRenderer 1.Word類&#xff1a; 12345678910111213141…

看看《System.CommandLine》

記得之前出過幾篇.net tool工具的文章&#xff0c;當時的做法是所有工具的語法分析全部自己解釋&#xff0c;自己執行&#xff0c;語法的解釋占了大部分時間&#xff0c;反而工具的功能被弱化了。其實微軟有一個CommandLine框架在緩慢的發展著&#xff0c;至今都沒有正式發布&a…

Sublime Text 3 import Anaconda 無法正常補全模塊名解決辦法

Sublime Text 3 Anaconda配置 在安裝Sublime Text3之后我們總會安裝一些插件&#xff0c;比如Python的Anaconda自動補全插件。但是&#xff0c;裝好之后發現import 時無法像別的IDE里面那樣自動補全模塊名&#xff0c;就像圖中一樣&#xff1a; 解決辦法在Sublime Text的git…

hdu - 2586 How far away ?(最短路共同祖先問題)

題目鏈接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid2586 最近公共祖先問題~~LAC離散算法 題目大意&#xff1a;一個村子里有n個房子&#xff0c;這n個房子用n-1條路連接起來&#xff0c;接下了有m次詢問&#xff0c;每次詢問兩個房子a,b之間的距離是多少。 很明顯…

Android之添加固定圖標到桌面

我的QQ群 1 需求 Android之添加固定圖標到桌面 2 部分實現 在AndroidManifest.xml里面添加如下權限 <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" /><uses-permission android:name="com.android.launcher.perm…

想做程序員?不同方向入門路線全解

學習計算機編程有很多方向如果你沒有一個正確的路線&#xff0c;那么就會&#xff1a; 就會跟上面所說的那樣&#xff0c;被迫成為一個全棧&#xff0c;這是比較尷尬的。 若你想比較準確的針對某個方向學習&#xff0c;那就繼續往下看吧。 一、程序員分為哪幾個方向 隨著…

【轉】OpenGL超級寶典筆記——紋理映射Mipmap

原文地址 http://my.oschina.net/sweetdark/blog/177812 , 感謝作者&#xff0c;若非法轉載請聯系本人。 目錄[-] MipmappingMipmap過濾構建Mip層Mipmaps 硬件生成LOD&#xff08;多細節層次&#xff09;偏好紋理對象管理多個紋理常駐紋理紋理優先級回顧Mipmapping Mipmap是一個…

【Microstation】第二章:Microstation三維建模基礎知識

本章的主要內容包括模型的顯示樣式(線框、光滑)、三維定位(V、T、S、F)、Microstation常見的坐標系統(世界坐標系、ACS輔助坐標系、精確繪圖坐標系、)和Microstation的工作區域(2D和3D)。 一、顯示樣式 二、三維定位 三維定位在Microstation中顯得尤為重要,常見…

xtrabackup對MySQL數據庫的備份及恢復教程

xtrabackup xtrabackup 是 percona 的一個開源項目&#xff0c;可以熱備份innodb &#xff0c;XtraDB,和MyISAM&#xff08;會鎖表&#xff09;。對MyISAM存儲引擎會鎖表&#xff0c;也是很郁悶的因為線上使用的是Innodb和MyISAM兩種存儲引擎&#xff0c;比較 頭疼&#xff01;…

實現 EF Core 6 自定義查詢標記

前言在《EF Core使用Simple Logging輸出日志》中&#xff0c;我們介紹了查詢標記 TagWith&#xff0c;它可以幫助我們快速定位到需要的日志&#xff1a;而在 .NET 6 中&#xff0c;新增了另外一個查詢標記 TagWithCallSite&#xff0c;它可以標記出代碼的位置&#xff1a;var u…

LeetCode: 14. Longest Common Prefix

Write a function to find the longest common prefix string amongst an array of strings. 大意就是&#xff0c;寫一個函數可以找到一個數組字符串中的最長前綴。 分析&#xff1a; 最長前綴的最大值為數組字符串中長度最短的字符&#xff0c;由最短字符串由后向前遞減可以得…

jQuery選擇器和選取方法

我們已經使用了帶有簡單Css選擇器的jQuery選取函數:$()。現在是時候深入了解jQuery選擇器語法&#xff0c;以及一些提取和擴充選中元素集的方法了。 一、jQuery選擇器 在CSS3選擇器標淮草案定義的選擇器語法中&#xff0c;jQuery支持相當完整的一套子集&#xff0c;同時還添加了…

0運維?微信小程序云開發增刪查改【05】

在創建小程序時&#xff0c;選擇云開發&#xff1a; 隨后進入項目之后&#xff0c;此時整個目錄如下&#xff1a; 此時我們如圖目錄即可找到首頁位置&#xff1a; 接著咱們清除 index.wxml 代碼內容&#xff1a; 在 index.wxml 中加入如下代碼&#xff1a; <view> …

Android之解決卸載app后再次安裝提示room數據庫錯誤

1、問題 目前只有一個google手機之前安裝了app,里面有room寫的數據庫&#xff0c;后面把app卸載了&#xff0c;再次安裝新的app(修改了數據庫里面的字段)&#xff0c;啟動奔潰。 2、分析 提示數據庫錯誤&#xff0c;很明顯就像以前的app里面的數據庫沒有刪除一樣&#xff0c;…