• [你必须知道的.NET]第十三回:从Hello, world开始认识IL


    发布日期:2007.7.22 作者:Anytao

    ©2007 Anytao.com ,原创作品,转贴请注明作者和出处。

    本文将介绍以下内容:

    • IL代码分析方法 
    • Hello, world历史 
    • .NET学习方法论

     

    1. 引言

    1988年Brian W. Kernighan和Dennis M. Ritchie合著了软件史上的经典巨著《The C programming Language》,我推荐所有的程序人都有机会重温这本历史上的经典之作。从那时起,Hello, world示例就作为了几乎所有实践型程序设计书籍的开篇代码,一直延续至今,除了表达对巨人与历史的尊重,本文也以Hello, world示例作为我们扣开IL语言的起点,开始我们循序渐进的IL认识之旅。

    2. 从Hello, world开始

    首先,当然是展示我们的Hello, world代码,开始一段有益的分享。

    using System;
    using System.Data;

    public class HelloWorld
    {
        
    public static void Main()
        {
            Console.WriteLine(
    "Hello, world.");
        }
    }

    这段代码执行了最简单的过程,向陌生的世界打了一个招呼,那么运行在高级语言背后真相又是什么呢,下面开始我们基于上述示例的IL代码分析。 

    3. IL体验中心

    对编译后的可执行文件HelloWorld.exe应用ILDasm.exe反编译工具,还原HelloWorld的为文本MSIL编码,至于其工作原理我们期望在系列的后续文章中做以交代,我们查看其截图为:

    Anytao.com
     

    由上图可知,编译后的IL结构中,包含了MANIFEST和HelloWorld类,其中MANIFEST是个附加信息列表,主要包含了程序集的一些属性,例如程序集名称、版本号、哈希算法、程序集模块等,以及对外部引用程序集的引用项;而HelloWorld类则是我们下面介绍的主角。

    3.1 MANIFEST清单分析

    打开MANIFEST清单,我们可以看到

    从这段IL代码中,我们的分析如下:

    • .assembly指令用于定义编译目标或者加载外部库。在IL清单中可见,.assembly extern mscorlib表示外部加载了外部核心库mscorlib,而.assembly HelloWorld则表示了定义的编译目标。值得注意的是,.assembly将只显示程序中实际应用到的程序集列表,而对于加入using引用的程序集,如果并未在程序中引用,则编译器会忽略多加载的程序集,例如System.Data将被忽略,这样就有效避免了过度加载引起的代码膨胀。
    • 我们知道mscorlib.dll程序集定义managed code依赖的核心数据类型,属于必须加载项。 例如接下来要分析的.ctor指令表示构造函数,从代码中我们知道没有为HelloWord类提供任何显示的构造函数,因此可以肯定其继承自基类System.Object,而这个System.Object就包含在mscorlib程序集中。
    • 在外部指令中还会指明了引用版本(.ver);应用程序实际公钥标记(.publickeytoken),公钥Token是SHA1哈希码的低8位字节的反序(如下图所示),用于唯一的确定程序集;还包括其他信息如语言文化等。

    • HelloWorld程序集中包括了.hash algorithm指令,表示实现安全性所使用的哈希算法,系统缺省为0x00008004,表明为SHA1算法;.ver则表示了HelloWorld程序集的版本号;
    • 程序集由模块组成, .module为程序集指令,表明定义的模块的元数据,以指定当前模块。
    • 其他的指令还有:imagebase为影像基地址;.file alignment为文件对齐数值;.subsystem为连接系统类型,0x0003表示从控制台运行;.corflags为设置运行库头文件标志,默认为1;这些指令不是我们研究的重点,详细的信息请参考MSDN相关信息。

    3.2 HelloWorld类分析

    首先是HelloWorld类,代码为:

    .class public auto ansi beforefieldinit HelloWorld
           extends [mscorlib]System.Object
    {
    // end of class HelloWorld
    • .class表明了HelloWorld是一个public类,该类继承自外部程序集mscorlib的System.Object类。
    • public为访问控制权限,这点很容易理解。
    • auto表明程序加载时内存的布局是由CLR决定的,而不是程序本身
    • ansi属性则为了在没有被管理和被管理代码间实现无缝转换。没有被管理的代码,指的是没有运行在CLR运行库之上的代码,例如原来的C,C++代码等。
    • beforefieldinit属性为HelloWorld提供了一个附加信息,用于标记运行库可以在任何时候执行类型构造函数方法,只要该方法在第一次访问其静态字段之前执行即可。如果没有beforefieldinit则运行库必须在某个精确时间执行类型构造函数方法,从而影响性能优化,详细的情况可以参与MSDN相关内容。 

    然后是.ctor方法,代码为:

    .method public hidebysig specialname rtspecialname 
            instance 
    void  .ctor() cil managed
    {
      
    // 代码大小       7 (0x7)
      .maxstack  8
      IL_0000:  ldarg.
    0
      IL_0001:  call       instance 
    void [mscorlib]System.Object::.ctor()
      IL_0006:  ret
    // end of method HelloWorld::.ctor
    • cil managed 说明方法体中为IL代码,指示编译器编译为托管代码。
    • .maxstack表明执行构造函数.ctor期间的评估堆栈(Evaluation Stack)可容纳数据项的最大个数。关于评估堆栈,其用于保存方法所需变量的值,并在方法执行结束时清空,或者存储一个返回值。
    • IL_0000,是一个标记代码行开头,一般来说,IL_之前的部分为变量的声明和初始化。
    • ldarg.0 表示装载第一个成员参数,在实例方法中指的是当前实例的引用,该引用将用于在基类构造函数中调用。
    • call指令一般用于调用静态方法,因为静态方法是在编译期指定的,而在此调用的是构造函数.ctor()也是在编译期指定的;而另一个指令callvirt则表示调用实例方法,它的调用过程有异于call,函数的调用是在运行时确定的,首先会检查被调用函数是否为虚函数,如果不是就直接调用,如果是则向下检查子类是否有重写,如果有就调用重写实现,如果没有还调用原来的函数,依次类推直到找到最新的重写实现。
    • ret表示执行完毕,返回。

    最后是Main方法,代码为:

    .method public hidebysig static void  Main() cil managed
    {
      .entrypoint
      
    // 代码大小       11 (0xb)
      .maxstack  8
      IL_0000:  ldstr      
    "Hello, world."
      IL_0005:  call       
    void [mscorlib]System.Console::WriteLine(string)
      IL_000a:  ret
    // end of method HelloWorld::Main
    • .entrypoint指令表明了CLR加载程序HelloWorld.exe时,是首先从.entrypoint方法开始执行的,也就是表明Main方法将作为程序的入口函数。每个托管程序必须有并且只有一个入口点。这区别于将Main函数作为程序入口标志。
    • ldstr指令表示将字符串压栈,"Hello, world."字符串将被移到stack顶部。CLR通过从元数据表中获得文字常量来构造string对象,值得注意的是,在此构造string对象并未出现在《第五回:深入浅出关键字---把new说透》中提到的newobj指令,对于这一点的解释我们将在下一回中做简要分析。
    • hidebysig属性用于表示如果当前类作为父类时,类中的方法不会被子类继承,因此HelloWorld子类中不会看到Main方法。

    接下来的一点补充:

    • 关于注释,IL代码中的注释和C#等高级语言的注释相同,其实编译器在编译IL代码时已经将所有的注释去掉,所以任何对程序的注释在IL代码中是看不见的。 

    3.3 回归简洁

    去粗取精,我们的IL代码可以简化,下面的代码是基于上面的分析,并去处不重要的信息,以更简洁的方式来展现的HelloWorld版IL代码,详细的分析就以注释来展开吧。

    4. 结论

    结束本文,我们从一个点的角度和IL来了一次接触,除了了解几个重要的指令含义,更重要的是已经走进了IL的世界。通过一站式的扫描HelloWorld的IL编码,我们还不足以从全局来了解IL,不过第一次的亲密接触至少让我们太陌生,而且随着系列文章的深入我们将逐渐建立起这种认知,从而提高我们掌握了解.NET底层的有效工具。本系列也将在后续的文章中,逐渐建立起这种使用工具的方法,敬请关注。

     

    参考文献

    (USA)Joe Duffy , Professional .NET Framework 2.0

    (USA)David Chappell, Understanding .NET

    CLR团队公告

    CLR基础研究团队成立了,本团队以研究和探求CLR基础和.NET Framework底层知识为宗旨,在此热烈的欢迎博客园的朋友们。如果对CLR及.NET底层研究有兴趣,请申请加入CLR基础研究团队,在一个专注的环境里共享你的技术。在申请之前请您阅读团队纲领,CLR团队信息如下:

    团队地址:http://clr.cnblogs.com/
    团队纲领:查看
    团队申请:进入

    团队近期活动:整理收集团队成员CLR系列文章,在团队公告展播。 

    温故知新

    [开篇有益]
    [第一回:恩怨情仇:is和as]
    [第二回:对抽象编程:接口和抽象类]
    [第三回:历史纠葛:特性和属性]
    [第四回:后来居上:class和struct]
    [第五回:深入浅出关键字---把new说透]
    [第六回:深入浅出关键字---base和this]
    [第七回:品味类型---从通用类型系统开始]
    [第八回:品味类型---值类型与引用类型(上)-内存有理]
    [第九回:品味类型---值类型与引用类型(中)-规则无边]
    [第十回:品味类型---值类型与引用类型(下)-应用征途]
    [第十一回:参数之惑---传递的艺术(上)]
    [第十二回:参数之惑---传递的艺术(下)]

    ©2007 Anytao.com

    原创作品,转贴请注明作者和出处,留此信息。

    本贴子以现状提供且没有任何担保,同时也没有授予任何权利。
    This posting is provided "AS IS" with no warranties, and confers no rights.

  • 相关阅读:
    「manacher」
    「回文自动机」
    「可持久化数据结构(平衡树、trie树、线段树) 」
    「后缀数组」
    「LCT」
    「网络流」
    「一些知识点」
    「至今不会」
    「推荐博客」
    「最小生成树」
  • 原文地址:https://www.cnblogs.com/anytao/p/must_net_13.html
Copyright © 2020-2023  润新知