聊聊 C# 方法重载的底层玩法
最近在看 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 去看这个程序的 静态反汇编代码,截图如下:
从代码上的注释可以清楚的看到,原来:
Person::sayhello(int)变成了j_?sayhello@Person@@QAEXH@Z。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# 的基因就已经决定了,如果大家了解 CLR 的话应该知道, C#中的类最终 ...
 - 聊聊 C# 和 C++ 中的 泛型模板 底层玩法
		
最近在看 C++ 的方法和类模板,我就在想 C# 中也是有这个概念的,不过叫法不一样,人家叫模板,我们叫泛型,哈哈,有点意思,这一篇我们来聊聊它们底层是怎么玩的? 一:C++ 中的模板玩法 毕竟 C+ ...
 - C#语法糖系列 —— 第一篇:聊聊 params 参数底层玩法
		
首先说说为什么要写这个系列,大概有两点原因. 这种文章阅读量确实高... 对 IL 和 汇编代码 的学习巩固 所以就决定写一下这个系列,如果大家能从中有所收获,那就更好啦! 一:params 应用层玩 ...
 - C#语法糖系列 —— 第二篇:聊聊 ref,in 修饰符底层玩法
		
自从 C# 7.3 放开 ref 之后,这玩法就太花哨了,也让 C# 这门语言变得越来越多范式,越来越重,这篇我们就来聊聊 ref,本质上来说 ref 的放开就是把 C/C++ 指针的那一套又拿回来了 ...
 - 聊聊 C# 中的多态底层 (虚方法调用) 是怎么玩的
		
最近在看 C++ 的虚方法调用实现原理,大概就是说在 class 的首位置存放着一个指向 vtable array 指针数组 的指针,而 vtable array 中的每一个指针元素指向的就是各自的 ...
 - 【腾讯云的1001种玩法】几种在腾讯云建立WordPress的方法(Linux)(二)
		
版权声明:本文由张宁原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/126547001488207964 来源:腾云阁 ht ...
 - java-方法重写和方法重载的区别
		
1.方法重载: - 英文:Overload - 可以改变返回值类型,只看参数列表. - 本类中出现的方法名一样,参数列表不同的方法,与返回值类型无关. 2.方法重写: - 英文:Override - ...
 - Java常见面试题02-方法重写和方法重载的区别?方法重载能改变返回值类型吗?
		
方法重写和方法重载的区别?方法重载能改变返回值类型吗? A:Override方法重写和Overload方法重载的区别? Overload是否可以改变返回值类型?可以 方法重写 • 子类中 ...
 - Java连载29-方法执行内存分析、方法重载
		
一.JVM包含三个内存区:栈内存.堆内存.方法区内存 二.注意点 (1)在MyEclipse中字体是红色的是一个类的名字,并且这个类除了我们自定义的类是JavaSE类库中自带的 (2)其实JavaSE ...
 
随机推荐
- Kubernetes 解决方案-图解
 - Fastjsonfan反序列链学习前置知识
			
Fastjson前置知识 Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象. Fastjson 可以操作任何 ...
 - gin框架使用【4.请求参数】
			
GET url: http://127.0.0.1:8080/users?id=1&name=卷毛狒狒 package mainimport ( "github.com/gin-go ...
 - 1903021116—吉琛—Java第三周作业—eclipse创建Java程序
			
项目 内容 课程班级博客链接 19级信计班 这个作业要求链接 eclipse如何创建java程序 java语言基础(上) 我的课程学习目标 1. 学习博客园更多使用技巧 2. 通过作业实践熟练与熟悉掌 ...
 - git 在 pull 或者合并分支的时候会遇到下图这个界面
			
可以不管(直接进入 3, 4 步), 如果要输入解释的话就需要 按键盘字母 i 进入 insert 模式 修改最上面那行黄色合并信息,可以不修改 // 黄色内容为默认的合并信息; 按键盘左上角 & ...
 - 6.1 SHELL脚本
			
6.1 SHELL脚本元素 第一行的脚本声明(#!)用来告诉系统使用哪种Shell解释器来执行该脚本: 第二行的注释信息(#)是对脚本功能和某些命令的介绍信息,使得自己或他人在日后看到这个脚本内容时, ...
 - 从零开始学YC-Framework之初步
			
本文主要内容为如下几个方面? YC-Framework的取名出于什么考虑? YC-Framework的特点有哪些? YC-Framework的模块由哪些组成? 为什么要开发YC-Framework? ...
 - for   &    while  &迭代器
			
for (int i = 0; i < 10; i++) { System.out.println("hello"); } int a=100; for (;a<110 ...
 - [洛谷] P1065 [NOIP2006 提高组] 作业调度方案
			
点击查看代码 #include<bits/stdc++.h> using namespace std; const int N = 1e6 + 10; int m, n, ans = 0; ...
 - 一、深入学习c++先要练好的内功
			
掌握进程虚拟地址空间区域的划分 课程讲的内容建立在x86 32位的Linux系统下. 任何的编程语言会产生两种东西:指令和数据.磁盘上的可执行文件在启动时都会加载到内存当中,但是不会加载到物理内存中, ...