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 );
}
};
我的引擎里面的一段代码!基本能阐述清楚原理了。睡觉咯~~~