聊聊c#方法重载的底层玩法(代码片段)

dotNET跨平台 dotNET跨平台     2022-10-20     435

关键词:

最近在看 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++ 这一套。

好了本篇就聊到这里,希望对你有帮助。

聊聊c#和c++中的泛型模板底层玩法(代码片段)

...我们叫泛型,哈哈,有点意思,这一篇我们来聊聊它们底层是怎么玩的?一:C++中的模板玩法毕竟C++是兼容C语言,而C是过程式 查看详情

聊聊c#和c++中的泛型模板底层玩法

...家叫模板,我们叫泛型,哈哈,有点意思,这一篇我们来聊聊它们底层是怎么玩的?一:C++中的模板玩法毕竟C++是兼容C语言,而C是过程式的玩法,所以C++就出现了两种模板类型,分别为:函数模板 查看详情

c#语法糖系列——第一篇:聊聊params参数底层玩法

首先说说为什么要写这个系列,大概有两点原因。这种文章阅读量确实高...对IL和汇编代码的学习巩固所以就决定写一下这个系列,如果大家能从中有所收获,那就更好啦!一:params应用层玩法首先上一段测试代码。classProgramstat... 查看详情

c#语法糖系列——第二篇:聊聊ref,in修饰符底层玩法

...C#这门语言变得越来越多范式,越来越重,这篇我们就来聊聊ref,本质上来说ref的放开就是把C/C++指针的那一套又拿回来了,而且还封装成一套自己的玩法,下面一一解读下。一:方法参数上的ref我想设计者的初心把ref 查看详情

聊聊c#中的多态底层(虚方法调用)是怎么玩的(代码片段)

最近在看C++的虚方法调用实现原理,大概就是说在class的首位置存放着一个指向vtablearray指针数组的指针,而vtablearray中的每一个指针元素指向的就是各自的虚方法,实现方式很有意思,哈哈,现在我很好... 查看详情

聊聊c#中的多态底层(虚方法调用)是怎么玩的

最近在看C++的虚方法调用实现原理,大概就是说在class的首位置存放着一个指向vtablearray指针数组的指针,而vtablearray中的每一个指针元素指向的就是各自的虚方法,实现方式很有意思,哈哈,现在我很好奇C#中如何实现的。一:C... 查看详情

聊聊c#clr中那些大量的友元函数,友元类的底层玩法(代码片段)

一:理解友元如果你看过CLR代码就会发现这里面有很多的friend修饰符,比如:MethodTable.cpp文件下。class MethodTable    /************************************     *  FRIEND FUNCTIONS     ************************************/    // DO  查看详情

c#视频方法重载函数重载传值(代码片段)

...函数重载ref传值重载重载,简单的来说就是函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。重载的定义函数名相同,函数... 查看详情

c#传智:方法及参数重载(第7天)(代码片段)

一、方法作用域   被调用者需要调用者的值,方法有二:   1.传参数.    privatestaticvoidMain(string[]args)          intm=3;      Console.WriteLine(m);      Console.ReadKey();        publicstaticintGetMax(intm)          returnm&... 查看详情

c#多态性学习,虚方法抽象方法接口等用法举例(代码片段)

C#多态性学习,虚方法、抽象方法、接口等用法举例1.多态性定义  C#中的多态性是OOP(面向对象编程)的一个基本概念,它允许一个对象在不同情况下表现出不同的行为,以增强代码的可重用性和灵活性。  根据网上的教程... 查看详情

是否可以让 c# 使用大多数特定类型而不是基类型的方法重载?

】是否可以让c#使用大多数特定类型而不是基类型的方法重载?【英文标题】:Isitpossibletogetc#tousemethodoverloadofmostspecifictyperatherthanbasetype?【发布时间】:2016-06-1616:42:00【问题描述】:如果您有一个使用派生类型重载的方法,则在... 查看详情

c#表达式树最完善的表达式树expression.dynamic的玩法(代码片段)

...4987967.html,其中,当时一直没有研究Expression.Dynamic的使用方法(因为网上找不到资料),就了解到是程序运行时动态去构建表达式树,举个例子,例如我们需要在我们的查询条件中去构建他是等于或者不等于,这个时候,虽然我们... 查看详情

C#中方法重载的不同行为

】C#中方法重载的不同行为【英文标题】:DifferentbehaviourofmethodoverloadinginC#【发布时间】:2011-02-1818:44:30【问题描述】:我在浏览C#Brainteasers(http://www.yoda.arachsys.com/csharp/teasers.html)时遇到了一个问题:这段代码的输出应该是什么?c... 查看详情

C#将双数组传递给构造函数重载方法

】C#将双数组传递给构造函数重载方法【英文标题】:C#Passingdoublearraytoconstructoroverloadmethod【发布时间】:2013-03-0507:09:49【问题描述】:我正在从一本书中学习c#,作为练习的一部分,我必须自己编写代码。要做的一件事是将双精... 查看详情

《c#零基础入门之百识百例》(三十六)方法重载--回文数

C#零基础入门函数--方法返回值前言一,方法重载定义1.1方法的签名信息1.2方法重载示例二,方法重载调用2.1解析步骤三,实例练习--回文数3.1题目描述3.2问题分析3.3参考代码前言本文属于C#零基础入门之百识百例系列文章。此系... 查看详情

C# 中的多个重载扩展方法

】C#中的多个重载扩展方法【英文标题】:MultipleOverloadedExtensionMethodsinC#【发布时间】:2015-01-2400:00:00【问题描述】:使用重载的扩展方法设置数据时遇到问题-我有两种扩展方法:publicstaticvoidFill(thisSearchRequestrequest,ISearchViewModelvm... 查看详情

c#中的方法重载问题

】c#中的方法重载问题【英文标题】:Methodoverloadingissueinc#【发布时间】:2011-07-3014:27:21【问题描述】:在一次采访中,他们曾问过这样一个问题一共有三种重载方法publicintAddtoatal(inta,intb)publicintAddtoatal(inta,intb,intc)publicfloatAddtoatal... 查看详情

聊聊dotnet7对bool与字符串互转的底层性能优化(代码片段)

本文也叫跟着StephenToub大佬学性能优化系列。大家都知道在.NET7有众多的性能优化,其中就包括了对布尔和字符串互转的性能优化。在对布尔和字符串的转换的性能优化上,有着非常巧妙的思路,值得写篇博客记录在 ... 查看详情