bitfan(数字世界一凡人)的专栏

在新浪博客的新家:http://blog.sina.com.cn/bitfan

用户操作
[即时聊天] [发私信] [加为好友]
金旭亮ID:bitfan
374649次访问,排名142好友0人,关注者60
=========
一名代码一写就十多年的“老古董级程序员”,看来还会再写下去;
一名有着“好为人师”臭毛病的小小讲师,“混迹于”大学校园,时不时发几句不合时宜的“反动言论”,好在皮厚,任人笑骂而无所谓;
于北京高楼林立之地租一小屋容身,依靠讲几节小课赚点小课时费应付房东大妈,所幸政府开恩照顾,终于让我等穷教师有了当房奴的资格,高呼:感谢政府!
==========
几本小书:
《网站建设教程》:高等教育出版社(2005)
《编程的奥秘——.NET软件技术学习与实践》:电子工业出版社(2006)
《.NET 2.0面向对象编程揭秘》:电子工业出版社(2007)。
《ASP.NET程序设计教程》高等教育出版社(2009)。

几堂小课:
在ITCAST(http://www.itcast.net)讲授.NET系列在线视频课程,想将课堂开到互联网上,目标不大,要帮助更多的年轻人学好技术找到好工作。野心不小,已录制了5个系列30节课,打算一路跟踪微软最新技术,打造国内自成体系、独特风格的微软技术系列课程。
bitfan的文章
原创 87 篇
翻译 0 篇
转载 0 篇
评论 2212 篇
最近评论
wjfmail:可以学,但估计要比别人多很多的努力.毕竟开发不是一种天赋就可以拥有的才能.你碰的钉子会比别人多得多.不过,还是从现在开始努力吧.
gsqswjh:我就是小学文化,而且"奔四"了,才开始学习编程,学如逆水行舟,不进则退,说的太对了,加油啊!
Microsoft_China_Vip:


www.soAsp.net 编程学习网 技术+ 实例应用 讲解不错。 推荐大家!

有很多 技术资料也很好!



Microsoft_China_Vip:


www.soAsp.net 编程学习网 技术+ 实例应用 讲解不错。 推荐大家!

有很多 技术资料也很好!



Microsoft_China_Vip:


www.soAsp.net 编程学习网 技术+ 实例应用 讲解不错。 推荐大家!

有很多 技术资料也很好!



文章分类
收藏
    相册
    .NET技术学习与实践
    5.2 使用.NET开发数据库应用程序
    数据绑定原理
    杂类
    存档
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 .NET值类型变量“活”在哪?收藏

    新一篇: 论《Java替代C语言的可能性》 | 旧一篇: 在.NET程序中小心使用String类型

     
    .NET值类型变量“活”在哪个堆栈中?
    ——MSIL学习笔记(一)
     
      金旭亮
           不管是什么语言编的.NET程序,最后都会被各自的编译器编译成MSIL。当程序运行时,.NET JIT编译器从程序集中读入IL指令并将其动态编译为可被本地CPU执行的机器指令再执行。
           程序集中的IL代码以二进制方式存在,人阅读起来相当不便,正如传统的Win32程序可以被反汇编成汇编程序,.NET程序集中的IL代码也可以被反汇编成易于阅读的IL汇编程序。如果您愿意的话,可以用任意一个文本编辑器直接撰写IL汇编源代码,然后使用ilasm.exe程序将其编译为包含二进制形式的IL指令。CLR只能执行二进制的IL指令。
           .NET SDK的另一个工具ildasm.exe可以用于将一个程序集反汇编为IL程序,在学习.NET时,这个工具非常有用,可以展示出高级语言(如C#和VB.NET)编写的程序是如何被CLR执行的。
           然而,相比C#和VB.NET的资料满天飞,MSIL的技术资料少得可怜。我能够查阅的只有MSDN中有关IL指令的文档(还只是针对Reflection.Emit名字空间中的类的),以及一本由Serge Lidin著的《inside Microsoft .NET IL assembler》, Serge Lidin是汇编器ilasm.exe工具的主要开发者,因此,他的书应具有相当的权威性,然而,这位技术牛人的写作水平实在不敢恭维,整本书象是一本参考手册。此书国内引进了中文版,然而翻译得很不好。幸运的是其光盘中附上了英文原版,实乃国人之大幸。
           IL可以看成是一个“面向对象的汇编语言”,它提供了许多指令直接对对象进行操作,比如newobj指令创建对象,box指令进行装箱等。
           IL指令的一个最重要特性是它是基于堆栈的。几乎每一条指令都要与堆栈打交道:或者向堆栈中Push一些数据,或者从中Pop一些数据。
           请看以下C#代码段:
        class Program
        {
            static void Main(string[] args)
            {
                int i = 100;
                int j = 200;
                int reslut = i + j;
            }
        }
    C#编译器将生成以下IL指令,其功能我在注释中有详细说明:
    .method private hidebysig static void Main(string[] args) cil managed
    {
     .entrypoint
     // 代码大小       15 (0xf)
     .maxstack 2
     .locals init ([0] int32 i,
               [1] int32 j,
               [2] int32 reslut)
     IL_0000: nop
     IL_0001: ldc.i4.s   100     //将100压入堆栈
     IL_0003: stloc.0     //从堆栈中弹出先前压入的100,传给局部变量i
     IL_0004: ldc.i4     0xc8     //将200压入堆栈
     IL_0009: stloc.1     //从堆栈中弹出先前压入的200,传给局部变量j
     IL_000a: ldloc.0     //将局部变量i的值压入堆栈
     IL_000b: ldloc.1     //将局部变量j的值压入堆栈
     IL_000c: add         //连继弹出两个整数,相加得300,又压入堆栈
     IL_000d: stloc.2     //从堆栈中弹出结果,保存到局部变量reslut中
     IL_000e: ret         //返回指令
    } // end of method Program::Main
     
           可以看到,所有的指令都涉及到堆栈。
           然而,我在研究IL汇编程序的时候,却被“堆栈”两个字弄糊涂了。
          几乎所有的C#书,都说值类型变量是生存在堆栈中,当函数结束时会自动销毁。那么,这里的堆栈与上述IL代码中的堆栈是不是一回事?
          请看上述IL程序中有一个MaxStack指令,查看资料,得知其含义是为evaluation stack保留两个槽(slot),注意,这里的堆栈英文原文是evaluation stack,MSDN中文版译为“计算堆栈”,slot可用于存放值对象,大小是可变的。换句话说,evaluation stack中的每一个slot可以存放一个值对象(对象引用也可看成是一种“特殊”的值变量,其值代表内存地址)或各种CLR直接支持的基本类型数据。
           从上述IL程序中可以很明显地看到,局部变量i,j和result绝不会生存于evaluation stack,因为它只有2个slot,而我们有3个变量。那它们“活在”在哪儿?
            IL程序中引人注目的一句是locals init指令,这提醒我们函数拥有另一块内存区域专用于存放局部变量,所以,声明为局部变量的值类型并不“活”在evaluation stack中。那么,为何所有的C#书(包括大名鼎鼎的Jeffrey Richter所著之《.NET框架程序设计》)都说值类型变量“活”在堆栈中?此堆栈在哪?至少有一点可以肯定,这个堆栈不会指的是evaluation stack。
            用ildasm.exe查看程序集清单(manifest),发现其中有一句:
           .stackreserve 0x00100000
          上述语句让CLR在装入程序集时保存1M的堆栈空间,这个空间供托管进程的托管线程使用,称为线程堆栈(Thread Stack)。既是线程堆栈,自然与线程相关,由于.NET托管进程可以创建多个托管线程,因此,每个线程也应该有自己的堆栈(Jeffrey Richter说也是1M,查看也是这位老先生写的《Windows核心编程》,说在Win2000在创建线程时其堆栈大小是可调整的)。
           .NET下每个托管线程都对应着一个线程函数,因此函数中定义的局部变量是在它拥有的线程堆栈中分配,而IL程序中的maxstack指令则从这一个1M的线程堆栈中再划出一块空间来作为evaluation stack。
          考虑一下函数调用的问题。
          IL使用call和callvirt两条指令调用特定类型所提供的方法。这就有一个函数参数传送的问题。以call指令为例,MSDN说在调用call指令之前,要将所有的实参压入evaluation stack,然后call指令再将其弹出,之后控制才会转到被调用的函数,而当被调用的函数执行完毕时,ret指令负责“将函数的返回值”从“被调用者的堆栈”(callee’s evaluation stack)复制到“调用者堆栈”(caller evaluation stack)中。您看MSDN文档中居然又出现了两个堆栈,是否有点晕了吗?
            查看Serge Lidin的书,他给出了这样一个图:
     
      
     
            如上图所示:CLR会给每一个被调用的方法分配三块内存,除了上面讲到的两块(Evaluation stack和局部变量表Local Variable table),还有一块是参数表(Argument table)。
          问题终于明晰了,call指令完成的工作应该是这样的:
     
         调用者按要调用函数的参数准备好实参,将它们压入“自己的”evaluation stack中,然后,call指令执行,它从调用者的evaluation stack弹出这些参数,放入被调用函数的Argument Table中。一切准备工作就绪,这时才开始执行被调用函数的第一条IL指令。
         当被调用函数执行完毕,如果有返回值,这个值应该被放在被调用函数自己的evaluation stack中(因为IL指令总是与堆栈打交道),然后,ret指令(每个函数最后一定是这条指令)将其弹出,再压入调用者的evaluation stack中,完成这一工作之后,执行流程转回到调用者。
          因此,线程每调用一个函数,将导致图中所示的三块区域在1M的线程堆栈中分配给调用函数,对于递归调用的情况,后调用的函数占用的内存区域将“压”在其调用者内存区域之上,每执行完一个函数,对应的栈顶指针移动一个位移(大小刚好等于此函数先前所占用的内存),从而导致这些内存被释放,其中的局部变量不再有效。
     
        分析.NET程序的IL指令还会得到一些有趣的结果,后面我会有更多的文章与网友们进行技术交流。
     
    注:由于手头的资料不足,此文所述内容仅是本人对CLR内部运行机理的一个推测,如有错误,敬请指正。by the way,望有网友能提供更多的MSIL技术资料信息,在此谢谢了。:-)
    转载请注明作者及出处。
     
     

    发表于 @ 2006年12月20日 21:33:00|评论(loading...)|编辑

    新一篇: 论《Java替代C语言的可能性》 | 旧一篇: 在.NET程序中小心使用String类型

    评论

    #hongyelzg 发表于2006-12-21 10:35:15  IP: 59.52.166.*
    除非你是汇编狂,或者想搞反汇编,或者你要开发一个基于.NET平台的编译器,否则根本没有必要去深入研究MSIL.这不是有时间没地方放嘛,哦,你是老师,时间多的是,
    #Analyst 发表于2006-12-21 14:02:21  IP: 222.71.48.*
    evaluation stack跟local stack完全是两个概念,MSIL是一套虚拟机指令集,对应的是一台抽象的堆栈式计算机,evaluation stack的作用是临时存放CPU指令运算的操作数,一般实际的CPU所对应的概念是寄存器。在MSIL转换成X86指令的时候evaluation stack就直接对应到寄存器上去了,而不是一块实际的内存,当然传递参数的时候还是要靠内存的,寄存器数量有限。把计算机体系结构再好好复习一遍,这些概念就清楚了。
    #pongba 发表于2006-12-21 14:15:25  IP: 222.94.3.*
    Analyst is totally right.
    #davidliu_net 发表于2006-12-21 18:35:40  IP: 203.81.22.*
    我想知道搞清楚这个问题的价值是什么?
    #bitfan 发表于2006-12-21 22:27:17  IP:
    to davidliu_net:

    前段时间看《CLR via C#》中文版,其中4.4节有关线程堆栈的那部分内容可能出现了排版错误,插图与正文严重不符,但又看不到E文版,而且Jeffrey Richter本人在书中也语焉不详,这引发了我的好奇,于是就自己分析IL汇编程序,顺带牵扯出了这篇短文。
    顺便说一下,CLR的内存对象布局模型有自己的特色,分析一下C#中隐藏、多态等特性是如何生成IL指令,以及CLR又是如何执行这些IL指令,其实挺有意思的,就象猜谜。
    当然,对于大多数一线开发人员来说,只要熟练掌握C#的面向对象特性也就够了,我研究这玩意儿只是出于好奇,想揭开CLR
    的面纱看一下罢了,Just For Fun。
    其实,我之所以进入软件这行,不也是因为喜欢吗?
    #Zricepig 发表于2006-12-22 06:44:44  IP: 221.222.79.*
    heap是堆,stack是栈
    “堆栈”看的真混乱
    #Zricepig 发表于2006-12-22 06:44:44  IP: 221.222.79.*
    heap是堆,stack是栈
    “堆栈”看的真混乱
    #macleo 发表于2006-12-25 06:32:18  IP:
    金老师的好文,谢谢!
    金老师辛苦了!
    #macleo 发表于2006-12-25 07:06:20  IP:
    晕,看晕了!
    #macleo 发表于2006-12-28 23:44:50  IP: 60.164.55.*
    不卧薪尝胆,怎能东山再起?

    怎么给删了呢?
    #dsj1234 发表于2006-12-29 09:28:55  IP: 219.132.249.*
    好文,不错。
    只是 result 单词写错了。
    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © bitfan(数字世界一凡人)