MSIL系列:关于Boxing和堆栈,栈的几个问题的回答

本来准备好好写一篇文章深入诠释下Boxing和unboxing的时候发生了些什么,并且好好讲下CLR的线程堆栈执行模型的.
累啊,近来事情也比较多,这里就简要的同意回答一下大家提出的问题吧.
就使用jiaoer的一段代码来说明问题:

namespace MSILTest
{
    public struct Point
    {
        public int x;
    }
    class Program
    {
        static void Main(string[] args)
        {
            Point p = new Point();
            p.x = 1;

            object o = p;
            p.x = 2;

            System.Console.WriteLine(p.x+","+o);
        }
    }
}
调试的过程中,输出的是2,MSILTest.Point
这里首先说明一下,输出的后面的一部分,MSILTest.Point是非常有学问的,通过研究这段程序的MSIL代码,以及反编译FCL中的String类的一个Concat方法的实现,以及类在继承中若干方法的重写,我们就可以了解为什么是输出MSILTest.Point;当然,如果Point是个类,里面只有一个int属性,就会直接输出int属性的值.

这里,p是2很好理解.我们在object o = p;这一行下一个断点,F5调试,然后F10单步执行两次.这时,鼠标放在o上面,可以看到o的值是1,而不是2.
这就说明了一个问题,boxing以后,原来的计算堆栈上面的值并没有删除;boxing只是复制这个valuetype到heap的对象的相应区域中.这个对象相应区域的valuetype这个时候就是值类型的另外一个类型:boxed type;
同时,在新生成了一个包含这个值类型数值的对象以后,返回这个对象的应用到计算堆栈的栈顶.

有一点大家要非常注意:unboxing并不包含对数值的复制.这点相当重要.
这里涉及到MSIL里面三个指令的等价关系的问题.
就是unbox.any这个指令下发生的事情,等于unbox指令发生的事情,加上ldobj这个指令发生的事情.box指令做的事情就是大家理解的拆箱,而ldobj执行的事情,是把这个值类型的数值复制到栈顶.
拆箱这个名词,指的是box这个指令发生的操作,并不包含数值的复制到栈顶这部分.所以MSIL又设计了一个unbox.any指令来完成这两个动作,这个非常广泛上的误区大家一定要区别清楚!

好了,下面来非常简单的说说线程堆栈,计算堆栈和堆栈,还有堆的区别.
这里设计到一个堆和栈的概念,大家通常说的堆就是托管堆,也就是heap.而通常说的栈就是计算堆栈,也就是stack.很多地方大家也习惯叫stack为堆栈.
而线程堆栈,里面涉及到的东西有多多了.当一个方法在执行的时候,一个外部存储和一个本地存储区域会分配给它.本地存储区域,会分配一个本地变量列表,一个计算堆栈,一个参变量表.这三个东西就是一个方法执行的时候需要的全部的东西.
本地存储区域作为一个field,和很多方法一起,放在了线程堆栈上面.每个现在正在执行的方法,就是线程堆栈上面最上面的方法.执行好了之后,pointer移动,这个方法就弹出去了.

所有的MSIL指令都是基于堆栈的,每个线程在分配的时候分配1m,这个在manifest文件里面有标识.
不过我算了好久就纳闷0x100000哪里是表示1m了....
在有一个就是我们研究linux操作系统在给线程分配空间的时候,应该是MmCreateMemoryArea这个方法指定了分配给一个线程的空间大小,好想是这个方法,看了之后就忘记了 :)
大家可以找漫谈"兼容内核之二十一.pdf"这么一个文件,里面有讲的.

额,回答大家的问题有点简略啊,呵呵,不好意思,很多东西都没写详细,扯起来又是得几千字才能说明白.
主要是最近有点累,事情也比较多.不知道给jiaoer讲清楚了没.

恩,鉴于国内在MSIL这方面的研究比较少,文档也不全,可能会写一个<<MSIL via CLR>>的系列吧,估计很多很多字,大家期待吧.

下一篇博文研究内容:.Net下PE文件详解

posted on 2007-10-28 14:17  lbq1221119  阅读(1094)  评论(13编辑  收藏  举报

导航