關(guān)于gcnew?? (轉(zhuǎn))
C++/CLI中使用gcnew關(guān)鍵字表示在托管堆上分配內(nèi)存,并且為了與以前的指針區(qū)分,用^來替換* ,就語義上來說他們的區(qū)別大致如下:
1.???? gcnew返回的是一個句柄(Handle),而new返回的是實際的內(nèi)存地址.
2.???? gcnew創(chuàng)建的對象由虛擬機托管,而new創(chuàng)建的對象必須自己來管理和釋放.
?
當(dāng)然,從程序員的角度來說,管它是句柄還是什么其他的東西,總跑不掉是對某塊內(nèi)存地址的引用,實際上我們都可以理解成指針.下面我們就寫一段代碼來測試一下好了.
?
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; } |
?
我把調(diào)試的時候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] ? |
?
? 我們先看分配內(nèi)存這部分的代碼
?
1.調(diào)用new方式分配
int* pInt = new int; 0000004c? mov???????? ecx,4 00000051? call??????? dword ptr ds:[03B51554h] |
可以看到,和以前在vc6中一樣,分配內(nèi)存的步驟如下:
1.? 首先把sizeof(int) = 4 放到ecx中
2.? 調(diào)用operator new 去分配4個字節(jié)
3.? 調(diào)用構(gòu)造函數(shù)等等......(這里不是我們的重點)
成功分配后,會把返回地址放在eax中。
?
2.調(diào)用gcnew方式分配
??? int^ rInt = gcnew int; 0000005d? mov???????? ecx,788EF9D8h 00000062? call??????? FCFAF66C 。。。 ??? Foo^ rFoo = gcnew Foo; 00000072? mov???????? ecx,3B51768h 00000077? call??????? FCFAF66C |
可以看到gcnew也是通過把一個參數(shù)放到ecx中,然后再調(diào)用一個函數(shù)來完成分配的操作,顯然0x788EF9D8應(yīng)該是一個地址,而不可能是一個數(shù)值。我們可以看到這里gcnew創(chuàng)建兩個不同類型的變量,調(diào)用的函數(shù)地址卻都是0xFCFAF66C,而存放到ecx中的兩個地址就不一樣。究竟這幾個地址代表什么呢?
?
和new一樣gcnew也是把返回地址放在eax中。我們直接從內(nèi)存窗口看eax指向的內(nèi)存塊好了。Aha,看到了沒有?
這次的eax = 0x00F73404? 對應(yīng)的內(nèi)存塊為
?
0x00F73404? d8 f9 8e 78 00 00 00 00 。。。 |
?
這個不就是 mov 到 ecx中的值么?再回憶昨天寫的分析Object對象布局的文章,可以肯定這個就是 MethodTable地址了,對于這個int來說,后面的4個字節(jié)對應(yīng)的就是存放它的RawData,比如如果你初始化為 4 那么內(nèi)存對應(yīng)的就變化為 d8 f9 8e 79 04 00 00 00
?
分析清楚存放到ecx中的是 MethodTable指針,我們再分析那個對應(yīng)的call函數(shù),從vm的代碼可以看出,有三個全局函數(shù)用來根據(jù)MethodTable創(chuàng)建對象,同時MethodTable本身也提供一個成員函數(shù)Allocate(),只不過這個成員函數(shù)也是調(diào)用的下面的函數(shù):
OBJECTREF AllocateObject( MethodTable *pMT )
OBJECTREF AllocateObjectSpecial( MethodTable *pMT )
OBJECTREF FastAllocateObject( MethodTable *pMT )
?
其中AllocateObject又是調(diào)用AllocateObjectSpecial來完成工作。那么我們調(diào)用的應(yīng)該就是AllocateObject或者FastAllocateObject了。
在我們的例子里面兩個call的地址都一樣,但是你如果寫下代碼 double ^ pDouble = gcnew double;這個時候的地址是多少?它和int 的一樣么?
目前我還沒有仔細去研究這個地址到底對應(yīng)的是該類型的MethodTable::Allocate()或是上面的這三個全局函數(shù),如果對應(yīng)MethodTable::Allocate(),那么2.0中應(yīng)該有個MethodTable::FastAllocate()吧,否則應(yīng)該就是對應(yīng)的全局函數(shù)AllocateObject 以及FastAllocateObject了。過幾天一定要抽空再好好研究一下。
?
下面看對應(yīng)的delete函數(shù)。
??? delete pInt; 000000d8? mov???????? ecx,dword ptr [esp+18h] 000000dc? call??????? dword ptr ds:[03B51540h] ? 比較簡單,就是傳入地址,然后調(diào)用operator delete來釋放類存,會調(diào)用析構(gòu)函數(shù) |
?
對應(yīng)的,釋放gcnew創(chuàng)建的對象的代碼如下:
??? delete rInt; 000000ac? mov???????? edx,edi 000000ae? mov???????? ecx,788F747Ch 000000b3? call??????? FC8D20FD |
這個也相對簡單,它對應(yīng)vm里面的一個函數(shù):
void? CallFinalizer(Thread* FinalizerThread, Object* fobj)
那么也就是
fobjà edx
FinalizerThread à ecx
Call CallFinalizer
?
但是,請注意!!!!!!!一個類包含析構(gòu)函數(shù)和不包含析構(gòu)函數(shù),它對應(yīng)的delete代碼是不一樣的,這點可以通過匯編代碼比較得到,我這里就不多說了。