struct STest
{
STest( void )
{
++iCount;
}
int iCount;
}
int main( void )
{
Stest obj;
obj.iCount = 0;
new( static_cast< void* >( &obj ) ) Stest();
return 0;
}
隨便寫了一個例子,能說明問題就行。
上面的紅色代碼調用了構造函數,由於構造函數中爲了計數,因此在再次調用構造函數之前先收工初始化成0.藍色那段代碼就是主題了。首先這裏會調用operator new( size_t, void* ) thow()。這個函數的原型是:
inline void *__cdecl operator new(size_t, void *_Where) _THROW0()
{ // construct array with placement at _Where
return (_Where);
}
這裏並沒有開闢新的空間。直接就返回了!那爲什麼後面還跟了個Stest()呢?而且語法也沒有錯誤。在new操作符執行完後。返回的就是我們傳進去的obj對象的地址。既然寫了Stest()那肯定就是要調用的。
但是這裏是創建新的對象呢?還是本來就是原來的對象呢?這裏只能反彙編裏面分析了。
首先看main函數:
00417A60 push ebp
00417A61 mov ebp,esp
00417A63 push 0FFFFFFFFh
00417A65 push offset __ehhandler$_main (425954h)
00417A6A mov eax,dword ptr fs:[00000000h]
00417A70 push eax
00417A71 mov dword ptr fs:[0],esp
00417A78 sub esp,0E8h
00417A7E push ebx
00417A7F push esi
00417A80 push edi
00417A81 lea edi,[ebp-0F4h]
00417A87 mov ecx,3Ah
00417A8C mov eax,0CCCCCCCCh
00417A91 rep stos dword ptr [edi]
00417A93 lea ecx,[obj]
00417A96 call STest::STest (4115DCh)
00417A9B mov dword ptr [obj],0
00417AA2 lea eax,[obj]
00417AA5 push eax
00417AA6 push 4
00417AA8 call operator new (411096h)
00417AAD add esp,8
00417AB0 mov dword ptr [ebp-0E0h],eax // new 操作符的返回值
00417AB6 mov dword ptr [ebp-4],0
00417ABD cmp dword ptr [ebp-0E0h],0
00417AC4 je main+79h (417AD9h)
00417AC6 mov ecx,dword ptr [ebp-0E0h] // 將返回值給ECX
00417ACC call STest::STest (4115DCh) // 調用構造函數
00417AD1 mov dword ptr [ebp-0F4h],eax
00417AD7 jmp main+83h (417AE3h)
00417AD9 mov dword ptr [ebp-0F4h],0
00417AE3 mov ecx,dword ptr [ebp-0F4h]
00417AE9 mov dword ptr [ebp-0ECh],ecx
00417AEF mov dword ptr [ebp-4],0FFFFFFFFh
00417AF6 xor eax,eax
00417AF8 push edx
00417AF9 mov ecx,ebp
00417AFB push eax
00417AFC lea edx,ds:[417B27h]
00417B02 call @ILT+480(@_RTC_CheckStackVars@8) (4111E5h)
00417B07 pop eax
00417B08 pop edx
00417B09 mov ecx,dword ptr [ebp-0Ch]
00417B0C mov dword ptr fs:[0],ecx
00417B13 pop edi
00417B14 pop esi
00417B15 pop ebx
00417B16 add esp,0F4h
00417B1C cmp ebp,esp
00417B1E call @ILT+1095(__RTC_CheckEsp) (41144Ch)
00417B23 mov esp,ebp
00417B25 pop ebp
00417B26 ret
首先紅色的指令是調用new操作符。完成之後將返回值eax放到ebp-0E0h中,第二條藍色的指令又把裏面的值給了ECX。這裏的目的就是爲了在構造函數中pop ecx。 這裏就關係到類對象調用成員函數的反彙編層面調用步驟。首先會將對象的地址給ECX。一個成員函數內部會比普通的函數多兩條指令。就是push ecx和pop ecx。先看看STest構造函數的反彙編代碼:
00411D90 push ebp
00411D91 mov ebp,esp
00411D93 sub esp,0CCh
00411D99 push ebx
00411D9A push esi
00411D9B push edi
00411D9C push ecx
00411D9D lea edi,[ebp-0CCh]
00411DA3 mov ecx,33h
00411DA8 mov eax,0CCCCCCCCh
00411DAD rep stos dword ptr [edi]
00411DAF pop ecx
00411DB0 mov dword ptr [ebp-8],ecx
00411DB3 mov eax,dword ptr [this]
00411DB6 mov ecx,dword ptr [eax]
00411DB8 add ecx,1
00411DBB mov edx,dword ptr [this]
00411DBE mov dword ptr [edx],ecx
00411DC0 mov eax,dword ptr [this]
00411DC3 pop edi
00411DC4 pop esi
00411DC5 pop ebx
00411DC6 mov esp,ebp
00411DC8 pop ebp
00411DC9 ret
紅色的push是爲了先把ecx讓出來執行藍色的mov ecx, 33h。沒有辦法,別人要用肯定先把自己的值給壓棧保存。在別人用完了後,會執行紅色的pop ecx。將剛纔壓入的ecx的值重新彈出到ecx中!再看下面綠色的兩條指令,將ecx給了[ebp-8]這裏剛好就是結構體對象的第一個字節的地址。原理就不多說了!這下this指針就是指向的剛開始傳進來的obj對象的地址了。之後就是加1.當然還可以其他操作。呵呵。便實現了構造函數多次調用!
這種一般用在申請已有空間等情況下:
template< class Ty >
class ALGA_API CAlgaAllocator
{
public:
CAlgaAllocator( void ){}
virtual ~CAlgaAllocator( void ){}
public:
Ty* allocate( size_t size )
{
return ( Ty* )internal_new( size * sizeof( Ty ) );
}
void deallocate( Ty* ptr )
{
internal_delete( ptr );
}
void construct( Ty* ptr, const Ty& elem )
{
new ( ( void* )ptr ) Ty( elem );
}
void destruct( Ty* ptr )
{
ptr->~Ty();
}
protected:
virtual void* internal_new( size_t size )
{
return operator new( size );
}
virtual void internal_delete( void* ptr )
{
operator delete( ptr );
}
};
我的引擎裏面的一段代碼!基本能闡述清楚原理了。睡覺咯~~~