CLR运行时细节 - 继承多态的实现

关于多态不多解释了,在运行时决定和调用具体的实现,是面向对象的基础 设计模式的基础.
准备把继承多态和接口多态分开,因为从CLR实现的角度继承多态相比于接口多态要简单得多,也更容易理解,本篇只讨论继承多态, .NET Framework 2.0 和 4.0 这两个版本在实现上稍微有点区别(这里先忽略方法Jit编译的过程,只关注实现的方式).

废话不多,先看代码: C# Polymorphism01.cs



using System;

using System.Runtime.CompilerServices;

public class Program

{

public static void Main(string[] args)

{

Console.WriteLine("Polymorphism01 demo");

BaseClass bc = new BaseClass();

BaseClass bc1 = new ChlidClass();

BaseClass bc2 = new BrotherClass();

BaseClass bc3 = new DerivedOfBrotherClass();

BrotherClass bc4 = new DerivedOfBrotherClass();

bc.VirtualFun1();

bc1.VirtualFun1();

bc2.VirtualFun1();

bc3.VirtualFun1();

bc3.VirtualFun2();

bc4.VirtualFun3();

Console.ReadLine();

}

}

public class BaseClass

{

[MethodImpl(MethodImplOptions.NoInlining)]

public virtual void VirtualFun1()

{

Console.WriteLine("BaseClass VirtualFun1");

}

[MethodImpl(MethodImplOptions.NoInlining)]

public virtual void VirtualFun2()

{

Console.WriteLine("BaseClass VirtualFun2");

}

}

public class ChlidClass : BaseClass

{

[MethodImpl(MethodImplOptions.NoInlining)]

public override void VirtualFun1()

{

Console.WriteLine("ChlidClass VirtualFun1");

}

[MethodImpl(MethodImplOptions.NoInlining)]

public override void VirtualFun2()

{

Console.WriteLine("ChlidClass VirtualFun2");

}

}

public class BrotherClass : BaseClass

{

[MethodImpl(MethodImplOptions.NoInlining)]

public override void VirtualFun1()

{

Console.WriteLine("BrotherClass VirtualFun1");

}

[MethodImpl(MethodImplOptions.NoInlining)]

public override void VirtualFun2()

{

Console.WriteLine("BrotherClass VirtualFun2");

}

[MethodImpl(MethodImplOptions.NoInlining)]

public virtual void VirtualFun3()

{

Console.WriteLine("BrotherClass VirtualFun3");

}

}

public class DerivedOfBrotherClass : BrotherClass

{

[MethodImpl(MethodImplOptions.NoInlining)]

public override void VirtualFun1()

{

Console.WriteLine("DerivedOfBrotherClass VirtualFun1");

}

[MethodImpl(MethodImplOptions.NoInlining)]

public override void VirtualFun2()

{

Console.WriteLine("DerivedOfBrotherClass VirtualFun2");

}

[MethodImpl(MethodImplOptions.NoInlining)]

public override void VirtualFun3()

{

Console.WriteLine("DerivedOfBrotherClass VirtualFun3");

}

}


  • 编译代码 先用 .Net Framework 2.0 编译:

    1

    2

    %windir%\Microsoft.NET\Framework\v2.0.50727\csc.exe /debug /target:exe /out:e:\temp\Polymorphism01_2.0.exe e:\temp\Polymorphism01.cs

    pause

  • 运行 Polymorphism01_2.0.exe

  • 启动windbg 附加进程 加载SOS

  • 查找对应的模块:
    !Name2EE *!Polymorphism01_2.0.exe



0:004> !Name2EE *!Polymorphism01_2.0.exe

Module: 790c1000 (mscorlib.dll)

--------------------------------------

Module: 00af2c5c (Polymorphism01_2.0.exe)


根据模块查找方法表:
!DumpModule -mt 00af2c5c

  • 先分别看下 BaseClass BrotherClass DerivedOfBrotherClass 这3个继承关系类的方法表(MethodTable)

可以看到第一个虚方法(ToString)的入口都是在方法表偏移28h的位置,其顺序是先父类,再子类,这样的安排让所有同一个家族(继承关系)的类型继承虚方法的顺序是一样的,并且偏移量是一样的,所有的类型(除了接口类型)的父类都是(或者间接是)System.Object,所以前4个虚方法肯定是Object里的4个虚方法(ToString Equals GetHashCode Finalize)

通过Program 的方法表(MethodTable)找到Main方法的入口地址:
!DumpMT -md 00af302c


0:004> !DumpMT -md 00af302c

EEClass: 00af12f4

Module: 00af2c5c

Name: Program

mdToken: 02000002  (E:\temp\Polymorphism01_2.0.exe)

BaseSize: 0xc

ComponentSize: 0x0

Number of IFaces in IFaceMap: 0

Slots in VTable: 6

--------------------------------------

MethodDesc Table

Entry MethodDesc      JIT Name

79286aa0   79104960   PreJIT System.Object.ToString()

79286ac0   79104968   PreJIT System.Object.Equals(System.Object)

79286b30   79104998   PreJIT System.Object.GetHashCode()

792f76d0   791049bc   PreJIT System.Object.Finalize()

00afc015   00af3024     NONE Program..ctor()

01010070   00af3018      JIT Program.Main(System.String[])

Main方法已经Jit编译,看看被编译成啥样子:
!u 01010070

  • 这里最重要的几行:


    01010139 8b4df8          mov     ecx,dword ptr [ebp-8]   // 这里是BaseClass实例对象的地址 放到 ecx寄存器,Jit采用类似fastcall的调用协定,前2个不大于4字节的参数用 ecx edx来传递,而实例方法的调用第一个参数是隐含的this指针(托管对象在托管堆上的地址),如果是静态方法就不需要传this pointer了

    0101013c 8b01            mov     eax,dword ptr [ecx]    // 托管堆上的对象(值类型装箱后也是一样)第一个4字节(64位8字节)是对象的方法表地址(MethodTable),这里是把方法表(MethodTable)地址赋给eax寄存器

    0101013e ff5038          call    dword ptr [eax+38h]    // 这里就是实际的方法调用 上面说了 第一个虚方法在方法表的偏移28h位置,前4个是Object里的4个虚方法,所以 VirtualFun1 的入口在方法表地址(MT) + 28h + 4×4字节 也就是偏移38h的位置

    01010141 90              nop

    01010142 8b4df4          mov     ecx,dword ptr [ebp-0Ch]    // 这里是 ChlidClass的对象地址赋给ecx

    01010145 8b01            mov     eax,dword ptr [ecx]    // 同样ChlidClass的方法表地址赋给eax

    01010147 ff5038          call    dword ptr [eax+38h]    // 调用ChlidClass方法表偏移38h的方法,也是VirtualFun1 方法

    0101014a 90              nop

    0101014b 8b4df0          mov     ecx,dword ptr [ebp-10h]    // BrotherClass的对象地址赋给ecx

    0101014e 8b01            mov     eax,dword ptr [ecx]    // BrotherClass方法表地址赋给eax

    01010150 ff5038          call    dword ptr [eax+38h]    // 调用BrotherClass方法表偏移38h的方法,也是VirtualFun1 方法

    01010153 90              nop

    01010154 8b4dec          mov     ecx,dword ptr [ebp-14h]    // DerivedOfBrotherClass的对象地址赋给ecx

    01010157 8b01            mov     eax,dword ptr [ecx]    // DerivedOfBrotherClass方法表地址赋给eax

    01010159 ff5038          call    dword ptr [eax+38h]     // 调用DerivedOfBrotherClass方法表偏移38h的方法,也是VirtualFun1 方法

    0101015c 90              nop

    0101015d 8b4dec          mov     ecx,dword ptr [ebp-14h]    // 还是DerivedOfBrotherClass对象地址

    01010160 8b01            mov     eax,dword ptr [ecx]    // DerivedOfBrotherClass的方法表赋给eax

    01010162 ff503c          call    dword ptr [eax+3Ch]    // 这次偏移不一样了,第6个方法 VirtualFun2 (28h+5×4字节)

    01010165 90              nop

    01010166 8b4de8          mov     ecx,dword ptr [ebp-18h]    // 还是DerivedOfBrotherClass对象地址

    01010169 8b01            mov     eax,dword ptr [ecx]    // DerivedOfBrotherClass的方法表赋给eax

    0101016b ff5040          call    dword ptr [eax+40h]    // 这次偏移又不一样了,第7个方法 VirtualFun3 (28h+6×4字节)

  • 可以看到 继承多态在CLR运行时的实现是通过方法表的偏移 间接调用的,而方法表内继承虚方法的构建顺序是先父类再子类,由于.NET是单一继承,这样就确保了在同一家族的同一虚方法的偏移量是一样的.

  • 接下来用Framework 4.0 编译下源码,4.0 和2.0相比 在实现上多了一层间接寻址,但思路是一样的



%windir%\Microsoft.NET\Framework\v4.0.30319\csc.exe /debug /target:exe /out:e:\temp\Polymorphism01_4.0.exe e:\temp\Polymorphism01.cs

pause


  • 运行 Polymorphism01_4.0.exe

  • 启动windbg 附加进程 加载SOS (这里要加载对于4.0的sos.dll)

  • 直接查找Main方法:
    !Name2EE Polymorphism01_4.0.exe Program.Main



0:004> !Name2EE Polymorphism01_4.0.exe Program.Main

Module:      00b32ea4

Assembly:    Polymorphism01_4.0.exe

Token:       06000001

MethodDesc:  00b33838

Name:        Program.Main(System.String[])

JITTED Code Address: 033a0070


  • 看Main方法的区别:!u 033a0070
    这里只截取最重要的一段,调用构造器和其他的部分都先忽略


    ...

    e:\temp\Polymorphism01.cs @ 16:

    033a0139 8b4df8          mov     ecx,dword ptr [ebp-8]  // 这个还是一样BaseClass对象的地址赋给ecx

    033a013c 8b01            mov     eax,dword ptr [ecx]    // 还是对象的第一个4字节是方法表地址 赋给eax

    033a013e 8b4028          mov     eax,dword ptr [eax+28h]    // 这里是和2.0的区别 所有继承的虚方法的起始地址保存在方法表偏移28h的位置,也就是偏移量不是从方法表地址开始算了

    033a0141 ff5010          call    dword ptr [eax+10h]    // 这里的方式一样的 eax是虚方法的起始位置了,前4个是Object的4个虚方法,偏移10h是第5个方法 VirtualFun1

    033a0144 90              nop

    e:\temp\Polymorphism01.cs @ 17:

    033a0145 8b4df4          mov     ecx,dword ptr [ebp-0Ch]    // ChlidClass对象地址赋给ecx

    033a0148 8b01            mov     eax,dword ptr [ecx]    // ChlidClass方法表地址赋给eax

    033a014a 8b4028          mov     eax,dword ptr [eax+28h]    // 虚表入口地址赋给eax

    033a014d ff5010          call    dword ptr [eax+10h]    //还是偏移到第5个方法 VirtualFun1

    033a0150 90              nop

    e:\temp\Polymorphism01.cs @ 18:

    033a0151 8b4df0          mov     ecx,dword ptr [ebp-10h]    // BrotherClass对象地址赋给ecx

    033a0154 8b01            mov     eax,dword ptr [ecx]     // BrotherClass方法表地址赋给eax

    033a0156 8b4028          mov     eax,dword ptr [eax+28h]    // 虚表入口地址赋给eax

    033a0159 ff5010          call    dword ptr [eax+10h]    //还是偏移到第5个方法 VirtualFun1

    033a015c 90              nop

    e:\temp\Polymorphism01.cs @ 19:

    033a015d 8b4dec          mov     ecx,dword ptr [ebp-14h]    // DerivedOfBrotherClass对象地址赋给ecx

    033a0160 8b01            mov     eax,dword ptr [ecx]    // DerivedOfBrotherClass方法表地址赋给eax

    033a0162 8b4028          mov     eax,dword ptr [eax+28h]    // 虚表入口地址赋给eax

    033a0165 ff5010          call    dword ptr [eax+10h]    //还是偏移到第5个方法 VirtualFun1

    033a0168 90              nop

    e:\temp\Polymorphism01.cs @ 20:

    033a0169 8b4dec          mov     ecx,dword ptr [ebp-14h]    // 上面同一个对象

    033a016c 8b01            mov     eax,dword ptr [ecx]

    033a016e 8b4028          mov     eax,dword ptr [eax+28h]

    033a0171 ff5014          call    dword ptr [eax+14h]    // 这里比上面的调用多偏移了4个字节 也就是第6个方法 VirtualFun2

    033a0174 90              nop

    e:\temp\Polymorphism01.cs @ 21:

    033a0175 8b4de8          mov     ecx,dword ptr [ebp-18h]    // 和上面不是同一个对象地址,但是是实例化同样类型的对象

    033a0178 8b01            mov     eax,dword ptr [ecx]

    033a017a 8b4028          mov     eax,dword ptr [eax+28h]

    033a017d ff5018          call    dword ptr [eax+18h]     // 这里比上面的调用再多偏移了4个字节 也就是第7个方法 VirtualFun3

    033a0180 90              nop

    ...

  • .NET 4.0 比2.0 多了一次间接寻址,就是先偏移到虚表的入口,再从这个入口开始偏移到相应的方法,这样的好处(个人觉得)虚表的存储位置可以更灵活 如果方法表(MT)包含多个可变长结构也没问题 只要入口地址保存在偏移28h的位置即可

参考文档:

https://www.microsoft.com/china/MSDN/library/netFramework/netframework/JITCompiler.mspx?mfr=true
http://www.codeproject.com/Articles/20481/NET-Type-Internals-From-a-Microsoft-CLR-Perspecti
http://blogs.microsoft.co.il/sasha/2012/03/15/virtual-method-dispatch-and-object-layout-changes-in-clr-40/
http://www.cnblogs.com/BlueTzar/articles/884694.html

相关文章:

  • CLR运行时细节 - Method Descriptor

原文地址:https://espider.github.io/CLR/inheritance-polymorphism/


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/326645.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2015蓝桥杯省赛---java---B---8(饮料换购)

题目 饮料换购 饮料换购乐羊羊饮料厂正在举办一次促销优惠活动。乐羊羊C型饮料,凭3个瓶盖可以再换一瓶C型饮料,并且可以一直循环下去,但不允许赊账。请你计算一下,如果小明不浪费瓶盖,尽量地参加活动,那么…

python123测验9程序题_python程序设计实验二

Python程序设计实验安徽工程大学班级:物流191 姓名:许岚岚 学号:3190505110日期:2020年3月21日指导教师:修 宇实验二 顺序结构程序设计(验证性实验)(二学时)【实验目的】(1)掌握数据的输入输出的方法&#…

Rider IDE恢复了对.NET Core调试的支持

近期发布的JetBrain的Rider IDE(EAP17)移除了对.NET Core调试支持。该功能与NuGet的一个软件包在许可上存在冲突,而EAP17使用NuGet提供的.NET Core项目调试功能,所以必须要移除该功能。为此,JetBrains迅速推出了Rider …

2016蓝桥杯省赛---java---B---1(煤球数目)

题目 煤球数目 思路分析 代码实现 package com.atguigu.lanqiao;import java.util.Scanner;public class Main { // 简单枚举public static void main(String[] args) { // 171700int pre 1;int plus 2;long sum 1;for (int k 2; k < 100; k) {sum (pre plus); //…

vue 多页面多模块分模块打包 分插件安装_Vue渲染方式

Vue中的渲染方式总结可分四种:原有模板语法&#xff0c;挂载渲染使用render属性&#xff0c;createElement函数直接渲染使用render属性&#xff0c;配合组件的template属性&#xff0c;createElement函数渲染使用render属性&#xff0c;配合单文件组件&#xff0c;createElemen…

零配置 之 Spring注解实现Bean定义

转载自 零配置 之 12.3 注解实现Bean定义 ——跟我学spring3 12.3 注解实现Bean定义 12.3.1 概述 前边介绍的Bean定义全是基于XML方式定义配置元数据&#xff0c;且在【12.2注解实现Bean依赖注入】一节中介绍了通过注解来减少配置数量&#xff0c;但并没有完全消除在XML…

开源库 Natasha2016 ,让IL编程跑起来

背景&#xff1a; IL编程在普通的程序员的代码里几乎不会出现&#xff0c;但从Json.net、Dapper、Asp.net等等开源项目都体现出了IL编程的重要性。 在IL编程的时候&#xff0c;上百行甚至上千行的IL代码实在让人头大&#xff0c;调试不方便不说&#xff0c;IL编程的逻辑也是不同…

代码随想录27期|Python|Day24|回溯法|理论基础|77.组合

图片来自代码随想录 回溯法题目目录 理论基础 定义 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。 回溯是递归的副产品&#xff0c;只要有递归就会有回溯。回溯函数也就是递归函数&#xff0c;指的都是一个函数。 基本问题 组合问题&#xff08;无序&…

指纹识别软件安装包下载

微信关注公众号&#xff1a;1111的博客&#xff0c;回复“指纹识别”&#xff0c;即可获取指纹识别安装包的下载链接。本软件的版本为指纹识别2.0应用程序&#xff0c;在安装的过程中有以下几个注意点【一定要看】&#xff1a;安装完成之后直接运行&#xff1a;指纹识别.exe文件…

2016蓝桥杯省赛---java---B---2(生日蜡烛)

题目描述 生日蜡烛 思路分析 代码实现(方式一) package com.atguigu.TEST;class Main{public static void main(String[] args) {int sum236;int a0;//记录开始过生日的年龄for (int i 0; i < 100; i) {for (int j i; j < 100; j) {aaj;if(asum){System.out.printl…

ntp时间同步会导致mysql关闭吗?_ntp时间同步问题解决方法

http://www.pool.ntp.org/zone/asia0.asia.pool.ntp.org1.asia.pool.ntp.org2.asia.pool.ntp.org3.asia.pool.ntp.org2.1 注册表信息修改PDC配置外部时间源设置#config external NTP serverw32tm.exe /config /manualpeerlist:”0.asia.pool.ntp.org,1.asia.pool.ntp.org,2.asi…

javaSE视频教程正式启动

亲爱的小伙伴们&#xff1a;经过激烈的思想斗争之后&#xff0c;我决定了&#xff0c;决定什么呢&#xff1f;决定给大家送福利了&#xff01;对&#xff0c;是送福利了&#xff01;送什么福利呢&#xff1f;送教程&#xff0c;javaSE的教程免费送&#xff0c;主要还是由我来讲…

快速搭建本地 .NET Core 运行时调试环境

需要的软件环境&#xff1a; Oracle VM VirtualBoxCentOS 7llvm lldb 3.6.0 (3.5.0我试过 dumpobj时候一直报无效参数 Invalid parameter T_T) 先在VirtualBox创建新虚机&#xff1a;一路 Next &#xff0c;文件位置可以自定义下(默认是在Users/当前用户/.. 目录下)创建完选在设…

背包问题+图解

图解 代码实现 package com.atguigu.dynamic;/*** 创建人 wdl* 创建时间 2021/4/3* 描述*/ public class KnapsackProblem {public static void main(String[] args) {int[] w{1,4,3};//物品的重量int[] val{1500,3000,2000};//物品的价值 这里的val[i]就是前面的v[i]int m4;/…

java循环结构教程

循环的语法我发一下&#xff1a;while( 布尔表达式 ) {//循环内容}do {//代码语句}while(布尔表达式);for(初始化; 布尔表达式; 更新) {//代码语句}今天录制了一下java 的循环结构视频&#xff0c;包括while,do while以及for循环&#xff0c;时间有点长&#xff0c;所以大家看的…

想和你一起为 Visual Studio 庆祝20岁生日

Visual Studio Live 倒计时开始…… 1997年1月28日&#xff0c;Visual Stuido 97正式推出&#xff0c;她将Visual Basic、Visual C、Visual J、Visual Foxpro、Visual InterDev聚合在一起&#xff0c;并且这个让人着迷的“小妖精”&#xff0c;统揽天下20年尚未有退位的意思。 …

2016蓝桥杯省赛---java---B---3(凑算式)

题目描述 凑算式 思路分析 通分 代码实现 package com.atguigu.TEST;class Main{static int a[]{1,2,3,4,5,6,7,8,9};static int ans;public static boolean check(){int xa[3]*100a[4]*10a[5];int ya[6]*100a[7]*10a[8];if((a[1]*ya[2]*x)%(y*a[2])0&&a[0](a[1…

微软.NET年芳15:我在Azure上搭建Photon服务器(C#.NET)

摘录网上的“.NET 15周年”信息如下&#xff1a; 微软的 .NET 框架本周迎来了 15 岁生日。.NET 的第一个版本在 2002 年 2 月 13 日作为的 Visual Studio.NET 的一部分首次公开亮相。过去 15 年&#xff0c;.NET 框架从一个流行的闭源软件开发平台&#xff0c;变成了一个开源的…

分治算法---汉诺塔

思路分析 代码实现 package com.atguigu.dac;public class Hanoitower {public static void main(String[] args) {hanoiTower(5,A,B,C);}//汉诺塔移动的方法//使用分治算法public static void hanoiTower(int num,char a,char b,char c){//如果只有一个盘if(num1){System.out…

.NET Core跨平台:使用.NET Core开发一个初心源商城总括

1..NET Core基本介绍 a 作为一个.NET的开发者&#xff0c;在以前的开发中&#xff0c;我们开发的项目基本都是部署在windows服务器上,但是在windows服务器上的话某些比较流行的解决访问量的方案基本都是先出现在linux上&#xff0c;而后才能迁移出现windows上&#xff0c;而且效…