精品主页 | 软件下载 | 系统下载 | 精品导航| 精彩图片 | 转帖工具 | 版主申请 | 影视下载

查看完整版本: 浅议C /CLI的gcnew关键字

mxh1998 2008-3-26 22:32

浅议C /CLI的gcnew关键字

  


                    C  /CLI中使用gcnew关键字表示在托管堆上分配内存,并且为了与以前的指针区分,用^来替换* ,就语义上来说他们的区别大致如下:









  1. gcnew返回的是一个句柄(Handle),而new返回的是实际的内存地址.




  2. gcnew创建的对象由虚拟机托管,而new创建的对象必须自己来管理和释放.





  当然,从程序员的角度来说,管它是句柄还是什么其他的东西,总跑不掉是对某块内存地址的引用,实际上我们都可以理解成指针.下面我们就写一段代码来测试一下好了.

















using namespace System;





ref class Foo


{


public:


Foo()


{


System::Console::WriteLine("Foo::Foo");


}


~Foo()


{


System::Console::WriteLine("Foo::~Foo");


}


public:


int m_iValue;


};





int _tmain()


{


int* pInt = new int;


int^ rInt = gcnew int;


Foo^ rFoo = gcnew Foo;





delete rFoo;


delete rInt;


delete pInt;


}





  我把调试的时候JIT编译的汇编代码择录了部分如下显示(请注意红色部分):


















int* pInt = new int;


0000004c mov ecx,4


00000051 call dword ptr ds:[03B51554h]


00000057 mov esi,eax


00000059 mov dword ptr [esp 18h],esi


int^ rInt = gcnew int;


0000005d mov ecx,788EF9D8h


00000062 call FCFAF66C


00000067 mov esi,eax


00000069 mov dword ptr [esi 4],0


00000070 mov edi,esi


Foo^ rFoo = gcnew Foo;


00000072 mov ecx,3B51768h


00000077 call FCFAF66C


0000007c mov esi,eax


0000007e mov ecx,esi


00000080 call dword ptr ds:[03B517ACh]


00000086 mov dword ptr [esp 1Ch],esi





delete rFoo;


0000008a mov ebx,dword ptr [esp 1Ch]


0000008e test ebx,ebx


00000090 je 000000A4


00000092 mov ecx,ebx


00000094 call dword ptr ds:[03FD0028h]


0000009a mov dword ptr [esp 14h],0


000000a2 jmp 000000AC


000000a4 mov dword ptr [esp 14h],0


delete rInt;


000000ac mov edx,edi


000000ae mov ecx,788F747Ch


000000b3 call FC8D20FD


000000b8 mov ebp,eax


000000ba test ebp,ebp


000000bc je 000000D0


000000be mov ecx,ebp


000000c0 call dword ptr ds:[03FD0020h]


000000c6 mov dword ptr [esp 10h],0


000000ce jmp 000000D8


000000d0 mov dword ptr [esp 10h],0


delete pInt;


000000d8 mov ecx,dword ptr [esp 18h]


000000dc call dword ptr ds:[03B51540h]








  


                  





  我们先看分配内存这部分的代码





  1.调用new方式分配
















int* pInt = new int;


0000004c mov ecx,4


00000051 call dword ptr ds:[03B51554h]




  可以看到,和以前在vc6中一样,分配内存的步骤如下:




  1. 首先把sizeof(int) = 4 放到ecx中




  2. 调用operator new 去分配4个字节




  3. 调用构造函数等等......(这里不是我们的重点)




  成功分配后,会把返回地址放在eax中。





  2.调用gcnew方式分配
















int^ rInt = gcnew int;


0000005d mov ecx,788EF9D8h


00000062 call FCFAF66C


。。。


Foo^ rFoo = gcnew Foo;


00000072 mov ecx,3B51768h


00000077 call FCFAF66C




  可以看到gcnew也是通过把一个参数放到ecx中,然后再调用一个函数来完成分配的操作,显然0x788EF9D8应该是一个地址,而不可能是一个数值。我们可以看到这里gcnew创建两个不同类型的变量,调用的函数地址却都是0xFCFAF66C,而存放到ecx中的两个地址就不一样。究竟这几个地址代表什么呢?





  和new一样gcnew也是把返回地址放在eax中。我们直接从内存窗口看eax指向的内存块好了。Aha,看到了没有?




  这次的eax = 0x00F73404 对应的内存块为

















0x00F73404 d8 f9 8e 78 00 00 00 00 。。。





  这个不就是 mov 到 ecx中的值么?再回忆昨天写的分析Object对象布局的文章,可以肯定这个就是 MethodTable地址了,对于这个int来说,后面的4个字节对应的就是存放它的RawData,比如如果你初始化为 4 那么内存对应的就变化为 d8 f9 8e 79 04 00 00 00





  分析清楚存放到ecx中的是 MethodTable指针,我们再分析那个对应的call函数,从vm的代码可以看出,有三个全局函数用来根据MethodTable创建对象,同时MethodTable本身也提供一个成员函数Allocate(),只不过这个成员函数也是调用的下面的函数:




OBJECTREF AllocateObject( MethodTable *pMT )


OBJECTREF AllocateObjectSpecial( MethodTable *pMT )


OBJECTREF FastAllocateObject( MethodTable *pMT )





  其中AllocateObject又是调用AllocateObjectSpecial来完成工作。那么我们调用的应该就是AllocateObject或者FastAllocateObject了。




  在我们的例子里面两个call的地址都一样,但是你如果写下代码 double ^ pDouble = gcnew double;这个时候的地址是多少?它和int 的一样么?




  目前我还没有仔细去研究这个地址到底对应的是该类型的MethodTable::Allocate()或是上面的这三个全局函数,如果对应MethodTable::Allocate(),那么2.0中应该有个MethodTable::FastAllocate()吧,否则应该就是对应的全局函数AllocateObject 以及FastAllocateObject了。过几天一定要抽空再好好研究一下。





  下面看对应的delete函数。
















delete pInt;


000000d8 mov ecx,dword ptr [esp 18h]


000000dc call dword ptr ds:[03B51540h]





比较简单,就是传入地址,然后调用operator delete来释放类存,会调用析构函数





  对应的,释放gcnew创建的对象的代码如下:
















delete rInt;


000000ac mov edx,edi


000000ae mov ecx,788F747Ch


000000b3 call FC8D20FD




  这个也相对简单,它对应vm里面的一个函数:




void CallFinalizer(Thread* FinalizerThread, Object* fobj)




  那么也就是




fobjà edx


FinalizerThread à ecx


Call CallFinalizer





  但是,请注意!!!!!!!一个类包含析构函数和不包含析构函数,它对应的delete代码是不一样的,这点可以通过汇编代码比较得到,我这里就不多说了。
页: [1]
查看完整版本: 浅议C /CLI的gcnew关键字