C#基础之IL

1.实例解析IL

  作为C#程序员,IL的作用不言而喻,首先来看一个非常简单的程序和它的IL解释图,通过这个程序的IL指令来简单的了解常见的IL指令是什么意思。

    class Program
    {
        static void Main(string[] args)
        {
            int i = 2;
            string str= "C#";
            Console.WriteLine("hello "+str);
        }
    }

接下来要明确一个概念,.NET运行时任何有意义的操作都是在堆栈上完成的,而不是直接操作寄存器。这就为.NET跨平台打下了基础,通过设计不同的编译器编译相同的IL代码来实现跨平台。对于堆栈我们的操作无非就是压栈和出栈,在IL中压栈通常以ld开头,出栈则以st开头。知道这个后再看上面的指令感觉一下子就豁然开朗了,接下来继续学习的步伐,下面的表格是对于一些常见ld指令。st指令则是将ld指令换成st,功能有压栈变为出栈,有时候会看到在st或ld后加.s这表示只取一个字节。再来看看流程控制,知道压出栈和流程控制后,基本上看出IL的大概意思那就冒闷踢啦。流程控制主要就是循环和分支,下面我写了个有循环和分支的小程序。其中我们用到了加法和比较运算,为此得在这里介绍最基本的三种运算:算术运算(add、sub、mul乘法、div、rem求余);比较运算(cgt大于、clt小于、ceq等于);位运算(not、and、or、xor异或、左移shl、右移shr)。要注意在比较运算中,当执行完指令后会直接将结果1或0压栈,这个过程是自动完成的。对于流程控制,主要是br、brture和brfalse这3条指令,其中br是直接进行跳转,brture和brture则是进行判断再进行跳转。

ldarg 加载成员的参数,如上面的ldarg.0
ldarga 装载参数的地址,注意一般加个a表示取地址
ldc 将数字常量压栈,如上面的ldc.i4.2
ldstr 将字符串的引用压栈
ldloc/ldloca ldloc将一个局部变量压栈,加a表示将这个局部变量的地址压栈
Ldelem 表示将数组元素压栈
ldlen 将数组长度压栈
ldind 将地址压栈,以地址来访问或操作数据内

 class Program
    {
        static void Main(string[] args)
        {
            int count = 2;
            string strName= "C#";
            if (strName == "C#")
            {
                for(int i=0;i<count;i++)
                    Console.WriteLine("hello C#");
            }
            else
                Console.WriteLine("ha ha");
        }
    }

2.面向对象的IL

  有了前面的基础后,基本上看一般的IL代码不会那么方了。如果我们在程序中声明一个类并创建对象,则在IL中可以看到newobj、class、instance、static等关键字。看IL指令会发现外部是类,类里面有方法,虽然方法里面是指令不过这和C#代码的结构是很相似的。从上面的这些现象可以很明显的感受到IL并不是简单的指令,它是面向对象的。当我们在C#中使用new创建一个对象时则在IL中对应的是newobj,另外还有值类型也是可以通过new来创建的,不过在IL中它对应的则是initobj。newobj用来创建一个对象,首先会分配这个对象所需的内存,接着初始化对象附加成员同步索引块和类型对象指针然后再执行构造函数进行初始化并返回对象引用。initobj则是完成栈上已经分配好的内存的初始化工作,将值类型置0引用类型置null即可。另外string是引用类型,从上面的例子可以看到一般是使用ldstr来将元数据中的字符串引用加载到栈中而不是newobj。但是如果在代码中创建string变量不是直接赋值而是使用new关键字来得到string对象,那么在IL中将会看到newobj指令。当创建一维零基数组时还会看到newarr指令,它会创建数组并将首地址压栈。不过如果数组不是一维零基数组的话仍将还是会看到我们熟悉的newobj。

  既然是面向对象的,那么继承中的虚方法或抽象方法在IL中肯定会有相应的指令去完成方法的调用。调用方法主要是call、callvirt、calli,call主要用来调用静态方法,callvirt则用来调用普通方法和需要运行时绑定的方法(也就是用instance标记的实例方法),calli是通过函数指针来进行调用的。不过也存在特殊情况,那就是call去调用虚方法,比如在密封类中的虚方法因为一定不可能会被重写因此使用call可提高性能。为什么会提高性能呢?不知道你是否还记得创建一个对象去调用这个对象的方法时,我们经常会判断这个对象是否为null,如果这个对象为null时去调用方法则会报错。之所以出现这种情况是因为callvirt在调用方法时会进行类型检测,此外判断是否有子类方法覆盖的情况从而动态绑定方法,而采用call则直接去调用了。另外当调用基类的虚方法时,比如调用object.ToString方法就是采用call方法,如果采用callvirt的话因为有可能要查看子类(一直查看到最后一个继承父类的子类)是否有重写方法,从而降低了性能。不过说到底call用来调用静态方法,而callvirt调用与对象关联的动态方法的核心思想是可以肯定的,那些采用call的特殊情况都是因为在这种情况下根本不需要动态绑定方法而是可以直接使用的。calli的意思就是拿到一个指向函数的引用,通过这个引用去调用函数,不过在我的学习中没有使用到这个,这个具体是如何拿到引用的我也不清楚,感兴趣者请自行百度。

3.IL的角色

  大家都知道C#代码编译后就会生成元数据和IL,可是我们常见的exe这样的程序集是如何生成的呢,它与IL是什么关系呢?首先有一点是可以肯定的,那就是程序集中肯定会包含元数据和IL,因为这2样东西是程序集中的核心。下面是一个描述程序集和内部组成图,从图中可以看出一个程序集是有多个托管模块组成的,一个模块可以理解为一个类或者多个类一起编译后生成的程序集。程序集清单指的是描述程序集的相关信息,PE文件头描述PE文件的文件类型、创建时间等。CLR头描述CLR版本、CPU信息等,它告诉系统这是一个.NET程序集。然后最主要的就是每个托管模块中的元数据和IL了。元数据用来描述类、方法、参数、属性等数据,.NET中每个模块包含44个元数据表,主要包括定义表、引用表、指针表和堆。定义表包括类定义表、方法表等,引用表描述引用到类型或方法之间的映射记录,指针表里存放着方法指针、参数指针等。可以看到元数据表就相当于一个数据库,多张表之间有类似于主外键之间的关系。

由前面的知识可以总结出IL是独立于CPU且面向对象的指令集。.NET平台将其之上的语言全都编译成符合CLS(公共语言规范)的IL指令集,接着再由不同的编译器翻译成本地代码,比如我们常见的JIT编译器,如果在Mac上运行C#可通过Mac上的特定编译器来将IL翻译成Mac系统能够执行的机器码。也就是说IL正如它的名字一样是作为一种中间语言来执行动态程序,比如我们调用一个方法表中的方法,这个方法会指向一个触发JIT编译器地址和方法对应的IL地址,于是JIT编译器便将这个方法指向的IL编译成本地代码。生成本地代码后这个方法将会有一条引用指向本地代码首地址,这样下次调用这个方法的时候将直接执行指向的本地代码。

时间: 2024-10-23 04:36:16

C#基础之IL的相关文章

IL 学习笔记

先上几篇博客链接: 一步步教你读懂NET中IL(图文详解) C#基础之IL 详解.NET IL代码 C# IL DASM 使用 你必须知道的.NET <C# to IL>.<Expert .NET 2.0 IL Assembler>等书籍的翻译博客

IL反编译的实用工具Ildasm.exe的使用方法

一.前言: 微软的IL反编译实用程序--Ildasm.exe,可以对可执行文件(ex,经典的控制台Hello World 的 exe 可执行文件)抽取出 IL 代码,并且给出命名空间以及类的视图.在讲述如何反编译之前,有必要从虚拟CPU的角度来看CLR,这样有助于先从正面了解代码执行过程. 虚拟CPU: .NET 程序,其核心皆为 CLR ,而同时CLR的功能却与CPU非常相近,其中CLR执行IL代码(或叫做,IL指令).操作数据,只不过操作的代码不同:CPU操作机器语言,而CLR操作IL代码.

浅析.NET IL代码

一.前言 IL是什么? Intermediate Language (IL)微软中间语言 C#代码编译过程? C#源代码通过LC转为IL代码,IL主要包含一些元数据和中间语言指令: JIT编译器把IL代码转为机器识别的机器代码.如下图 语言编译器:无论是VB code还是C# code都会被Language Compiler转换为MSIL MSIL的作用:MSIL包含一些元数据和中间语言指令 JIT编译器的作用:根据系统环境将MSIL中间语言指令转换为机器码 为什么ASP.NET网站第一次运行时

.NET 重新学习 系列

为什么要重新学习? 说来也奇怪,虽然各种语言,框架发展地越来越好,按理来说我们写程序也应该越来越容易. 但是现在看来,一些基础性的内容反而也变得越来越重要了,例如内存一致性,多线程调度等等问题,对基础不掌握或者一知半解我们很难写出高效的代码. 虽然网上的基础教程非常多了,没有必要再去写了,但我觉得知己动手去学习比看别人的东西更深入. 文章围绕NET 基础 和IL语言编写: 1.NET重新学习   关键字 2.NET重新学习   值类型和引用类型 3.NET重新学习   性能优化 4.NET重新学

java web 开发三剑客 -------电子书

Internet,人们通常称为因特网,是当今世界上覆盖面最大和应用最广泛的网络.根据英语构词法,Internet是Inter + net,Inter-作为前缀在英语中表示“在一起,交互”,由此可知Internet的目的是让各个net交互.所以,Internet实质上是将世界上各个国家.各个网络运营商的多个网络相互连接构成的一个全球范围内的统一网,使各个网络之间能够相互到达.各个国家和运营商构建网络采用的底层技术和实现可能各不相同,但只要采用统一的上层协议(TCP/IP)就可以通过Internet

CLR基础,CLR运行过程,使用dos命令创建、编译、运行C#文件,查看IL代码

CLR是Common Language Runtime的缩写,是.NET程序集或可执行程序运行的一个虚拟环境.CLR用于管理托管代码,但是它本身是由非托管代码编写的,并不是一个包含了托管代码的程序集,所以不能使用IL DASM进行查看,但CLR以dll的形式位于.NET版本号文件夹内. □ C#源代码从编译到CLR运行的全过程 →编写C#源代码,以class,struct,enum,interface,delegate...的形式 →编译器把源代码编译成.dll或.exe,其中包含了一些重要信息

C# to IL 2 IL Basics(IL基础)

This chapter and the next couple of them will focus on and elicit a simple belief of ours,that if you really want to understand C# code in earnest, then the best way of doing so isby understanding the IL code generated by the C# compiler.So, we shall

如鹏网.Net基础1 第一章:.Net入门

------------------------------------------------ 重点提示: 1.程序的注释:单行注释.多行注释: ------------------------------------------------ 第1节 .Net学习路线及几个容易混淆的概念 C#过程编程语法 C#面向对象基础语法 基本类库 大量案例练习 Mysql sqlserver Ado.net 大量案例练习 综合项目 后续 HTML/JavaScript/三层架构/Asp.net/项目 三

C# 基础 学习 之 数据类型

首先 要知道 C# 的 基础 数据类型 并没有 内置于 C# 的语言中,而是内置于 .net Framework 中的, 当我们 声明 一个 类型时,例如(int),实际是 得到的 一个 .net结构System.Int32的一个实例.因为 net 支持 多种 语言的 编码(跨语言),他们最终 会 转化成 中间语言 IL; 这样的好处有 1. 确保了 强制 类型 安全 2. 实现了 不同语言的 交互性 3. 所有数据 都是对象, 所以 有属性 ,有 方法, 比如  把 一个字符串 “123” 转