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


    最近在看 C++ 的方法和类模板,我就在想 C# 中也是有这个概念的,不过叫法不一样,人家叫模板,我们叫泛型,哈哈,有点意思,这一篇我们来聊聊它们底层是怎么玩的?

    一:C++ 中的模板玩法

    毕竟 C++ 是兼容 C 语言,而 C 是过程式的玩法,所以 C++ 就出现了两种模板类型,分别为:函数模板类模板,下面简单分析一下。

    1. 函数模板的玩法

    玩之前先看看格式: template <typename T> rettype funcname (parameter list) { }

    说实话,我感觉 C++ 这一点就做的非常好,人家在开头就特别强调了,这是一个 template,大家不要搞错了,按照这个格式,我们来一个简单的 Sum 操作,参考代码如下:

    
    #include <iostream>
    
    //求和函数
    template <typename T> T getsum(T  t1, T  t2) {
    	return t1 + t2;
    }
    
    int main() {
    
    	int sum1 = getsum<int>(10, 10);
    
    	long sum2 = getsum<long>(20, 20);
    
    	printf("output: int:sum=%d, long: sum=%ld", sum1, sum2);
    }
    
    

    接下来我就很好奇,这种玩法和 普通方法 调用有什么不同,要想找到答案,可以用 IDA 去看它的静态汇编代码。

    从静态反汇编代码看,当前生成了两个函数符号分别为: j_??$getsum@H@@YAHHH@Zj_??$getsum@J@@YAJJJ@Z,现在我们就搞清楚了,原来一旦给 模板 指定了具体类型,它就生成了一个新的函数符号。

    乍一看这句话好像没什么问题,但如果你心比较细的话,会发现一个问题,如果我调用两次 getsum<int> 方法,那会生成两个具体函数吗? 为了寻找答案,我们修改下代码:

    
    int main() {
    
    	int sum1 = getsum<int>(10, 10);
    
    	int sum2 = getsum<int>(15, 15);
    }
    
    

    然后再用 IDA 查看一下。

    哈哈,可以发现这时候并没有生成一个新的函数符号,其实往细处说:j_??$getsum@H@@YAHHH@Z函数签名组合出来的名字,因为它们签名一致,所以在编译阶段必然就一个了。

    2. 类模板的玩法

    首先看下类模板的格式:template <typename T1, typename T2, …> class className { };

    还是那句话,开头一个 template 暴击,告诉你这是一个模板 😄😄😄, 接下来上一段代码:

    
    #include <iostream>
    
    template <typename T> class Calculator
    {
    public:
    	T getsum(T a1, T b1) {
    		return a1 + b1;
    	}
    };
    
    int main() {
    
    	Calculator<int> cal1;
    	int sum1 = cal1.getsum(10, 10);
    
    	Calculator<long> cal2;
    	int sum2 = cal2.getsum(15, 15);
    
    	printf("output: sum1=%d, sum2=%ld", sum1,sum2);
    }
    
    

    接下来直接看 IDA 生成的汇编代码。

    从上面的方法签名组织上看,有点意思,类名+方法名 柔和到一个函数符号上去了,可以看到符号不一样,说明也是根据模板实例化出的两个方法。

    二:C# 中的模板玩法

    接下来我们看下 C# 中如何实现 getsum 方法,当我把代码 copy 到 C# 中,我发现不能实现简单的 泛型参数 加减乘除操作,这就太搞了,网上找了下实现方式,当然也可以让 T 约束于 unmanaged,那就变成指针玩法了。

    
    namespace ConsoleApp1
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                Calculator<int> calculator1 = new Calculator<int>();
                Calculator<long> calculator2 = new Calculator<long>();
    
                int sum1 = calculator1.getsum(10, 10);
    
                long sum2 = calculator2.getsum(15, 15);
    
                Console.WriteLine($"sum={sum1}, sum2={sum2}");
                Console.ReadLine();
            }
        }
    
        public class Calculator<T> where T : struct, IComparable
        {
            public T getsum(T a1, T b1)
            {
                if (typeof(T) == typeof(int))
                {
                    int a = (int)Convert.ChangeType(a1, typeof(int));
                    int b = (int)Convert.ChangeType(b1, typeof(int));
    
                    int c = a + b;
                    return (T)Convert.ChangeType(c, typeof(T));
                }
                else if (typeof(T) == typeof(float))
                {
                    float a = (float)Convert.ChangeType(a1, typeof(float));
                    float b = (float)Convert.ChangeType(b1, typeof(float));
    
                    float c = a + b;
                    return (T)Convert.ChangeType(c, typeof(T));
                }
                else if (typeof(T) == typeof(double))
                {
                    double a = (double)Convert.ChangeType(a1, typeof(double));
                    double b = (double)Convert.ChangeType(b1, typeof(double));
    
                    double c = a + b;
                    return (T)Convert.ChangeType(c, typeof(T));
                }
                else if (typeof(T) == typeof(decimal))
                {
                    decimal a = (decimal)Convert.ChangeType(a1, typeof(decimal));
                    decimal b = (decimal)Convert.ChangeType(b1, typeof(decimal));
    
                    decimal c = a + b;
                    return (T)Convert.ChangeType(c, typeof(T));
                }
    
                return default(T);
            }
        }
    }
    
    

    那怎么去看 Calculator<int>Calculator<long> 到底变成啥了呢? 大家应该知道,C# 和 操作系统 隔了一层 C++,所以研究这种远离操作系统的语言还是有一点难度的,不过既然隔了一层 C++ ,那在 C++ 层面上必然会有所反应。

    如果你熟悉 CLR 的类型系统,应该知道 C# 所有的 类 在其上都有一个 MethodTable 类来承载,所以它就是鉴别我们是否生成多个个体的依据,接下来我们用 WinDbg 查看托管堆,看看在其上是如何呈现的。

    
    0:008> !dumpheap -stat
    Statistics:
                  MT    Count    TotalSize Class Name
    00007ff9d37638e0        1           24 ConsoleApp1.Calculator`1[[System.Int64, System.Private.CoreLib]]
    00007ff9d3763800        1           24 ConsoleApp1.Calculator`1[[System.Int32, System.Private.CoreLib]]             
    
    

    从输出信息看,C++ 层面变成了两个 methodtable 类,如果不信的化,还可以分别查看 mt 下的所有方法。

    
    0:008> !dumpmt -md 00007ff9d37638e0
    MethodDesc Table
               Entry       MethodDesc    JIT Name
    ...
    00007FF9D36924E8 00007ff9d37638d0    JIT ConsoleApp1.Calculator`1[[System.Int64, System.Private.CoreLib]]..ctor()
    00007FF9D36924E0 00007ff9d37638c0    JIT ConsoleApp1.Calculator`1[[System.Int64, System.Private.CoreLib]].getsum(Int64, Int64)
    
    0:008> !dumpmt -md 00007ff9d3763800
    --------------------------------------
    MethodDesc Table
               Entry       MethodDesc    JIT Name
    00007FF9D36924D0 00007ff9d37637f0    JIT ConsoleApp1.Calculator`1[[System.Int32, System.Private.CoreLib]]..ctor()
    00007FF9D36924C8 00007ff9d37637e0    JIT ConsoleApp1.Calculator`1[[System.Int32, System.Private.CoreLib]].getsum(Int32, Int32)
    
    

    从输出信息看,getsum(Int64, Int64)getsum(Int32, Int32) 方法的入口地址 Entry 是完全不一样的,所以它们是完全独立的个体。

    三:总结

    当看到 模板泛型 两个词,我感觉前者更 通俗易懂 一些,当给模板赋予不同类型时将会生成新的实例,在 C/C++ 中直接化为不同的函数符号,在 C# 中会生成不同的 MethodTable,由于 C# 远离机器, 所以尽量谈到 C++ 层面即可 🤣🤣🤣

    图片名称
  • 相关阅读:
    uniapp实现底部弹出菜单选择
    【C++动态规划 多重背包】1774. 最接近目标价格的甜点成本|1701
    华为机试真题 Java 实现【磁盘容量排序】
    Android RecycleView列表实现画廊效果
    图像&视频编辑工具箱MMEditing安装及使用示例(Inpainting)
    excel每行按模板导出为一个excel文件,可以指定列文本生成二维码或者条形码
    高质量SaaS客户支持如何开发?5步教你如何打造!!
    Discord OAuth2授权以及机器人监听群事件
    Redis解决数据一致性方案
    企业微信之——扫码登录
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/16384668.html