一、前言

首先,想说说为什么要写这样系列的文章,有时候在和同事朋友聊天的时候,经常会听到这样的话题:

(1)在这家公司没什么长进,代码太烂,学不到东西。(你有没有想想框架为什么这样写,代码还可以怎么去优化,比如公司使用Dapper,源码研究过没以及这样封装原因是啥)

(2)现在只会Ctrl + C  Ctrl +V  ,不排除有时为了效率,包括我自己有时候也懒的写直接复制粘贴  (是不是感觉距离语言的本质越来越远了)

(3)Ctrl + C  Ctrl +V 时间长了,都有点怀疑自己是否有勇气面试其他公司 (是不是总给自己找借口,年龄大了,不敢疯狂了,当然大家不要误解,我没鼓励大家跳槽)

(4)干了几年没什么提高 (无论要精通那门技术,我们都应该从其本质出发)

最近也在反思自己,之前看到博客园大神:fish-li 的一篇文章《Fish Li 该如何帮助您呢?》其中说到:如何做一个有追求的技术人员,受益匪浅。以及张善友老师分享的关于雷果果的技术之路,大家都羡慕这些大神,何曾想过他们背后的付出,不要再抱怨环境不好,环境好也是给这些有准备和有追求的人,很感谢有这样的前辈,现在的社会确实很浮躁,但与我何干,好了毒鸡汤就灌到这里,大家如果有共鸣的话,不要表达出来了,默默想想。

二、IL指令

(1)什么是IL

IL(Intermediate Language),它也称为CIL或者MSIL,中文就是“中间语言”。IL由ECMA组织(ECMA-335标准)提供完整的定义和规范。我们可以直接把C#源码编译为.exe或dll文件,但是此时编译出来的程序代码并不是CPU能直接执行的二进制代码,而是IL代码。

(2)反编译工具

在这里使用ILDasm.exe,不适用其他的反编译工具,这个工具在安装完毕VS2010后就已经存在了,大家可以在开始菜单中输入“IL”关键字进行搜索,如果没有的话,到C:\Program Files(x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX4.0Tools

(3)IL 指令表

IL指令表

三、使用工具查看IL代码

案例一:

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string helloString = "Hello World";
Console.WriteLine(helloString);
}
}
}

从上图可以看到IL结构:包含MANIFEST文件和ConsoleApplication1,其中MANIFEST是一个清单文件,主要包括程序集的一些属性,例如程序集名称、版本号、哈希算法、程序集模块,以及对外部引用程序的引用项目。.Program类是我们要主要介绍的内容。

(1)MANIFEST清单文件介绍

// Metadata version: v4.0.30319
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C E0 ) // .z\V.4..
.ver :::
}
.assembly ConsoleApplication1
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( )
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 4E 6F 6E // ....T..WrapNonEx
6F 6E 6F ) // ceptionThrows. // --- 下列自定义属性会自动添加,不要取消注释 -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 ) .custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 6F 6E 6F 6C 6C // ...ConsoleApplic
6F 6E ) // ation1..
.custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( )
.custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( )
.custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( )
.custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 6F 6E 6F 6C 6C // ...ConsoleApplic
6F 6E ) // ation1..
.custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 6F C2 A9 // ...Copyright ..
) // 2018..
.custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( )
.custom instance void [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::.ctor(bool) = ( )
.custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 2D // ..$b52968c2-dfc3
2D 2D 2D // -4e81-82d2-d9f5b
) // b3283d7..
.custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 2E 2E 2E ) // ...1.0.0.0..
.custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 1C 2E 4E 6D 6F 6B // ....NETFramework
2C 6F 6E 3D 2E 2E // ,Version=v4.5.2.
0E 6D 6F 6B // .T..FrameworkDis
6C 4E 6D 2E 4E // playName..NET Fr
6D 6F 6B 2E 2E ) // amework 4.5.2
.hash algorithm 0x00008004
.ver :::
}
.module ConsoleApplication1.exe
// MVID: {A09A2101-9A49-483A-A224-5D2E14D231A6}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00020003 // ILONLY 32BITREQUIRED
// Image base: 0x01110000

介绍:

指定当前程序集需要引用的外部程序集,如上面 ConsoleApplication1.exe就是引用了mscorlib.

.publickeytoken = (标记 ) 指定所引用程序集的实际公钥标记。公钥能唯一确定程序集。

.ver:指定引用程序集的版本

.assembly 程序集名称  指定程序集名称

.hash algorithm int32值  :  指定使用的hash算法

.ver :指定程序集版本号

.module ConsoleApplication1.exe    指定组成程序集的模块名称,在此示例中,程序集中只包含一个文件
.subsystem 0x0003 // WINDOWS_CUI  指定程序要求的应用程序环境。在此示例中,0x0003表示该可执行文件从控制台运行
.corflags 0x00020003 // ILONLY 32BITREQUIRED 当前是元数据中的一个保留字段

.class表示的Program是一个类,extends 代表Program类继承于程序集mscorlib中的System.Object类,这就告诉我们,在C#中所有的类的父类都是Object。

private为访问权限,表明该类是私有的。

auto:表明程序加载的时候内存布局是有CLR决定的,而不是由程序本身控制的。

ansi:表明类的编码为ansi编码

beforefieldinit :表明CLR可以在第一次访问静态字段之前的任何时刻执行类型构造函数。类型构造函数也就是构造函数,而使用beforefieldinit属性可以提高性能。

.ctor 表示构造函数

cil managed 表明方法体中的代码是IL代码,且是托管代码,即运行在CLR运行库中的代码

.maxstack 表明执行构造函数时,评估堆栈可容纳数据项的最大个数。评估堆栈是保存方法中所需变量的值的一个内存区域,该区域在方法执行结束时会被清空,或者存储一个返回值

IL_0000是代码行的开头。一般在IL_标记之前的部分为变量的声明和初始化操作。

ldarg.0表明加载第一个成员参数,其中ldarg是load argument 的缩

call 指令一般用于调用静态方法,而这段代码中call指令并不是在调用静态函数,而是调用System.Object构造函数。另外一个指令则一般用来调用实例方法,它的调用过程是:首先检查被调用的函数是否为虚函数,

如果不是就直接调用,如果是则检查子类是否重写,如果有重写就调用子类中的实现,如果没有重写就继续调用原来函数。

ret 指令表示执行完毕,就是return的缩写

最后是Main函数,它是应用程序的入口函数:

hidebysig :指令表示如果当前类作为父类,用该指令标记的方法将不会被子类继承

.entrypoint :指令代表该函数是程序的入口函数,每个托管应用程序都有且只有一个入口函数,CLR加载的时候,首先从.entrypoint函数开始执行。

.locals init ([0] string helloString) 表示定义string 类型的变量,变量名成为:helloString

IL_0000: nop 表示不做任何操作 No Operation

ldstr "Hello World" :  ldstr:推送对元数据中存储的字符串的新对象引用   表示:将字符串“Hello World” 压入评估栈,此时“Hello World” 处于评估栈的栈定,栈是一种数据结构,具有先进后出的特性。

stloc.0  :从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中(也就helloString)  在此示例中:就是把字符串"Hello World" 赋值给变量helloString

ldloc.0 :将索引 0 处的局部变量加载到计算堆栈上。也就是:把变量helloString 加载到计算堆栈上

(以ld为前缀的指令表示:入栈操作  st为前缀的指令则代表着出栈操作)

call :指令表示调用静态函数, 这里调用的是Console类中的WriteLine函数,把第0个局部变量输出到控制台中

案例二:

(1)IL基本类型

任何都有其内置的类型,IL语言也不例外,因为C#语言最终都会编译成IL代码,所以两者必然存在一种对应关系:

(2)IL变量的声明

.locals 指令代表变量的声明,声明语句放在IL_标记前面。例如在前面的程序中,

.locals init ([0] string helloString)  就声明了一个名为helloString的变量,其中类型为string

(3)基本运算符

算数运算符:加法指令add   、乘法指令sub、出发指令div、以及求余指令rem等

位运算符:包括一元指令not 、与指令and、或指令or,结果以1 和 0 分别表示真、假,运算结果压入评估栈栈顶

比较运算:包括大于指令cgt、小于指令clt 和等于指令 ceq

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ILAdd
{
class Program
{
static void Main(string[] args)
{
int i = ;
int j = ; int result = i + j; Console.WriteLine(result); }
}
}

IL代码分析如下:

.method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
// 代码大小 17 (0x11)
.maxstack
//声明三个变量
.locals init ([] int32 i,
[] int32 j,
[] int32 result)
//不做任何操作
IL_0000: nop
//将整数值 2以4字节 作为 int32 推送到计算堆栈上
IL_0001: ldc.i4.
//把评估栈顶的值弹出,并赋值给第0个局部变量(即 i),等价于i = 2
IL_0002: stloc.
//将整数值 3以4字节 作为 int32 推送到计算堆栈上
IL_0003: ldc.i4.
//把评估栈顶的值弹出,并赋值给第1个局部变量(即 j),等价于j = 2
IL_0004: stloc.
//把第0个变量压入评估栈,即把变量 i 压入评估栈
IL_0005: ldloc.
//把第1个变量压入评估栈,即把变量 j 压入评估栈
IL_0006: ldloc.
//执行add操作,之后将把变量i和j清空,并把操作结果保存在评估栈站顶
IL_0007: add
//把站顶的值弹出,并赋值给第二个局部变量(即result) ,此时result即为i+j 的值,因为栈顶为两个值的和
IL_0008: stloc.
//将索引 2 处的局部变量加载到计算堆栈上。就是result
IL_0009: ldloc.
//call调用静态函数
IL_000a: call void [mscorlib]System.Console::WriteLine(int32)
IL_000f: nop
//返回
IL_0010: ret
} // end of method Program::Main
static void Main(string[] args)
{
int i = ;
if (i>)
{
Console.WriteLine("i为正数");
}
else
{
Console.WriteLine("i为0或负数");
}
}

IL分析如下:

.method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
// 代码大小 40 (0x28)
.maxstack
.locals init ([] int32 i,
[] bool V_1)
IL_0000: nop
IL_0001: ldc.i4.
IL_0002: stloc.
IL_0003: ldloc.
IL_0004: ldc.i4.
//执行大于指令,比较i与0,运算结果存在于评估栈栈顶,1表示真,即i>0为真
IL_0005: cgt
//就是把比较后的值赋值个变量V_1
IL_0007: stloc.
// 把变量V_1压入评估栈
IL_0008: ldloc.
//如果 value 为 false、空引用或零,则将控制转移到目标指令
IL_0009: brfalse.s IL_001a
IL_000b: nop
//推送对元数据中存储的字符串的新对象引用。
IL_000c: ldstr bytearray ( 3A 4E 6B ) // i.:Nckpe
IL_0011: call void [mscorlib]System.Console::WriteLine(string)
IL_0016: nop
IL_0017: nop
//无条件地将控制转移到目标指令(短格式)
IL_0018: br.s IL_0027
IL_001a: nop
//推送对元数据中存储的字符串的新对象引用。
IL_001b: ldstr bytearray ( 3A 4E 1F 8D ) // i.:N0..b..pe
IL_0020: call void [mscorlib]System.Console::WriteLine(string)
IL_0025: nop
IL_0026: nop
IL_0027: ret
} // end of method Program::Main

案例三:const的本质

namespace ConstIL
{
class Program
{
static void Main(string[] args)
{
       
Console.WriteLine(Person.num);
}
} public class Person
{
/// <summary>
/// 这个就是一个所谓的const常量
/// </summary>
public const int num = ;
}
}

IL代码分析:

为什么可以直接类名.num?这种语法只有在该常量为static修饰是才可以,下面我们来看看IL:

.field public static literal int32 num = int32(0x0000000A)

看到没,const其实就是一个static的变量,一个静态的值,因为它是跟着类走的。而不是实例。所以 const的特征如下:

(1)固定不变的值。

(2)在编译的时候就已经确定了。

(3)在初始化的时候设置值

好了,先写到这里,回家前写这一篇,希望对你有帮助。

参考书籍:《Learning Hard》

参考文章:http://www.cnblogs.com/flyingbirds123/archive/2011/01/29/1947626.html

作者:郭峥

出处:http://www.cnblogs.com/runningsmallguo/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

C#基础拾遗系列之一:先看懂IL代码的更多相关文章

  1. C#基础拾遗系列之二:使用ILSpy探索C#7.0新增功能点

    C#基础拾遗系列之二:使用ILSpy探索C#7.0新增功能点   第一部分: C#是一种通用的,类型安全的,面向对象的编程语言.有如下特点: (1)面向对象:c# 是面向对象的范例的一个丰富实现, 它 ...

  2. pyhton pandas数据分析基础入门(一文看懂pandas)

    //2019.07.17 pyhton中pandas数据分析基础入门(一文看懂pandas), 教你迅速入门pandas数据分析模块(后面附有入门完整代码,可以直接拷贝运行,含有详细的代码注释,可以轻 ...

  3. 从零开始一起学习SLAM | 理解图优化,一步步带你看懂g2o代码

    首发于公众号:计算机视觉life 旗下知识星球「从零开始学习SLAM」 这可能是最清晰讲解g2o代码框架的文章 理解图优化,一步步带你看懂g2o框架 小白:师兄师兄,最近我在看SLAM的优化算法,有种 ...

  4. 读懂IL代码就这么简单 ---- IL系列文章

    读懂IL代码就这么简单 (一) 读懂IL代码就这么简单(二) 读懂IL代码就这么简单(三)完结篇 出处:http://www.cnblogs.com/zery/tag/IL%20%E7%B3%BB%E ...

  5. 读懂IL代码就这么简单(三)完结篇

    一 前言 写了两篇关于IL指令相关的文章,分别把值类型与引用类型在 堆与栈上的操作区别详细的写了一遍 这第三篇也是最后一篇,之所以到第三篇就结束了,是因为以我现在的层次,能理解到的都写完了,而且个人认 ...

  6. 读懂IL代码就这么简单(二)

    一 前言 IL系列 第一篇写完后 得到高人指点,及时更正了文章中的错误,也使得我写这篇文章时更加谨慎,自己在了解相关知识点时,也更为细致.个人觉得既然做为文章写出来,就一定要保证比较高的质量,和正确率 ...

  7. 读懂IL代码就这么简单 (一)

    一前言 感谢 @冰麟轻武 指出文章的错误之处,现已更正 对于IL代码没了解之前总感觉很神奇,初一看完全不知所云,只听高手们说,了解IL代码你能更加清楚的知道你的代码是如何运行相互调用的,此言一出不明觉 ...

  8. 读懂IL代码就这么简单

    原文地址:http://www.cnblogs.com/zery/p/3366175.html 一前言 感谢 @冰麟轻武 指出文章的错误之处,现已更正 对于IL代码没了解之前总感觉很神奇,初一看完全不 ...

  9. 【转载】读懂IL代码就这么简单(三)完结篇

    一 前言 写了两篇关于IL指令相关的文章,分别把值类型与引用类型在 堆与栈上的操作区别详细的写了一遍这第三篇也是最后一篇,之所以到第三篇就结束了,是因为以我现在的层次,能理解到的都写完了,而且个人认为 ...

随机推荐

  1. bzoj:1673 [Usaco2005 Dec]Scales 天平

    Description Farmer John has a balance for weighing the cows. He also has a set of N (1 <= N <= ...

  2. HDU2504-又见GCD-递归

    又见GCD Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submi ...

  3. 2017ecjtu-summer training # 9 HDU 4544

    湫湫系列故事--消灭兔子 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Tota ...

  4. 用gcc编译c语言程序以及其编译过程

    对于初学c语言编程的我们来说,学会如何使用gcc编译器工具,对理解c语言的执行过程,加深对c语言的理解很重要!!! 1.预编译 --> 2.编译 --> 3.汇编 --> 4.链接- ...

  5. 了解 Python 语言中的时间处理

    python 语言对于时间的处理继承了 C语言的传统,时间值是以秒为单位的浮点数,记录的是从1970年1月1日零点到现在的秒数,这个秒数可以转换成我们日常可阅读形式的日期和时间:我们下面首先来看一下p ...

  6. bean的作用域 :singleton和prototype

  7. Win10下安装RabbitMQ以及基本知识学习

    一.为什么选择RabbitMQ?      先说一下场景,这是我们公司遇到,当然我这里不做业务评价哈?虽然我知道他很不合理,但是我是无能为力的.APP端部分注册是Java开发的系统,然后业务端是C#开 ...

  8. 分布式监控系统--zabbix

    1Zabbix简介 Zabbix 是一个企业级的分布式开源监控方案. 2.监控系统架构 C/S架构 客户端/服务器端,这种架构适合规模较小,处于同一地域的环境 C/P/S 客户端/代理端/服务器端/, ...

  9. nth-child()选择器小结

    之前也用到过nth-child,只是当时理解的不够透彻.今天回过头去看这个知识点时,发现了一个易错点. 浏览器支持情况: 所有主流浏览器都支持nth-child()选择器,除了IE8及更早的版本. 下 ...

  10. Linux的ls命令在Windows中的应用

    Linux的ls命令在Windows中的应用 注:ls是Linux中的命令.其作用是列出当前目录下的文件与文件夹.效果等同于Wndows中的dir指令. 如下图 下面是详细步骤 步骤一.在桌面新建一个 ...