重识new之二
下面仅以VS调试子功能——查看汇编代码进行分析,以VS2012源码为例:int main( )
{
00E282E0push ebp
00E282E1mov ebp,esp
00E282E3push 0FFFFFFFFh
00E282E5push 0E29400h
00E282EAmov eax,dword ptr fs:
00E282F0push eax
00E282F1sub esp,220h
00E282F7push ebx
00E282F8push esi
00E282F9push edi
00E282FAlea edi,
00E28300mov ecx,88h
00E28305mov eax,0CCCCCCCCh
00E2830Arep stos dword ptr es:
00E2830Cmov eax,dword ptr ds:
00E28311xor eax,ebp
00E28313mov dword ptr ,eax
00E28316push eax
00E28317lea eax,
00E2831Amov dword ptr fs:,eax
MyClass* fPtr1 = new MyClass;
00E28320push 4
MyClass* fPtr1 = new MyClass;
00E28322call operator new (0E21410h)
00E28327add esp,4
00E2832Amov dword ptr ,eax
00E28330mov dword ptr ,0
00E28337cmp dword ptr ,0
00E2833Eje main+73h (0E28353h)
00E28340mov ecx,dword ptr
00E28346call MyClass::MyClass (0E210BEh)
00E2834Bmov dword ptr ,eax
00E28351jmp main+7Dh (0E2835Dh)
00E28353mov dword ptr ,0
00E2835Dmov eax,dword ptr
00E28363mov dword ptr ,eax
00E28369mov dword ptr ,0FFFFFFFFh
00E28370mov ecx,dword ptr
00E28376mov dword ptr ,ecx
delete fPtr1;
00E28379mov eax,dword ptr
00E2837Cmov dword ptr ,eax
00E28382mov ecx,dword ptr
00E28388mov dword ptr ,ecx
00E2838Ecmp dword ptr ,0
00E28395je main+0CCh (0E283ACh)
00E28397push 1
00E28399mov ecx,dword ptr
00E2839Fcall MyClass::`scalar deleting destructor' (0E2101Eh)
00E283A4mov dword ptr ,eax
00E283AAjmp main+0D6h (0E283B6h)
00E283ACmov dword ptr ,0
MyClass* fPtr2 = new( nothrow ) MyClass;
00E283B6push 0E30228h
00E283BBpush 4
00E283BDcall operator new (0E210E1h)
00E283C2add esp,8
00E283C5mov dword ptr ,eax
00E283CBmov dword ptr ,1
00E283D2cmp dword ptr ,0
00E283D9je main+10Eh (0E283EEh)
00E283DBmov ecx,dword ptr
00E283E1call MyClass::MyClass (0E210BEh)
00E283E6mov dword ptr ,eax
00E283ECjmp main+118h (0E283F8h)
00E283EEmov dword ptr ,0
00E283F8mov eax,dword ptr
00E283FEmov dword ptr ,eax
00E28404mov dword ptr ,0FFFFFFFFh
00E2840Bmov ecx,dword ptr
00E28411mov dword ptr ,ecx
delete fPtr2;
00E28414mov eax,dword ptr
00E28417mov dword ptr ,eax
00E2841Dmov ecx,dword ptr
00E28423mov dword ptr ,ecx
00E28429cmp dword ptr ,0
00E28430je main+167h (0E28447h)
00E28432push 1
00E28434mov ecx,dword ptr
00E2843Acall MyClass::`scalar deleting destructor' (0E2101Eh)
00E2843Fmov dword ptr ,eax
00E28445jmp main+171h (0E28451h)
00E28447mov dword ptr ,0
char x1;
MyClass* fPtr3 = new( &x1 ) MyClass;
00E28451mov eax,1
00E28456imul eax,eax,0
00E28459lea ecx,x1
00E2845Dpush ecx
00E2845Epush 4
00E28460call operator new (0E2105Ah)
00E28465add esp,8
00E28468mov dword ptr ,eax
00E2846Emov dword ptr ,2
00E28475cmp dword ptr ,0
00E2847Cje main+1B1h (0E28491h)
00E2847Emov ecx,dword ptr
00E28484call MyClass::MyClass (0E210BEh)
00E28489mov dword ptr ,eax
00E2848Fjmp main+1BBh (0E2849Bh)
00E28491mov dword ptr ,0
00E2849Bmov edx,dword ptr
00E284A1mov dword ptr ,edx
00E284A7mov dword ptr ,0FFFFFFFFh
00E284AEmov eax,dword ptr
00E284B4mov dword ptr ,eax
fPtr3 -> ~MyClass();
00E284B7push 0
00E284B9mov ecx,dword ptr
00E284BCcall MyClass::`scalar deleting destructor' (0E2101Eh)
MyClass* fPtr4 = new MyClass;
00E284C1push 0Ch
00E284C3call operator new[] (0E2119Ah)
00E284C8add esp,4
00E284CBmov dword ptr ,eax
00E284D1mov dword ptr ,3
00E284D8cmp dword ptr ,0
00E284DFje main+23Bh (0E2851Bh)
00E284E1mov eax,dword ptr
MyClass* fPtr4 = new MyClass;
00E284E7mov dword ptr ,2
00E284EDpush 0E21023h
00E284F2push 0E210BEh
00E284F7push 2
00E284F9push 4
00E284FBmov ecx,dword ptr
00E28501add ecx,4
00E28504push ecx
00E28505call `eh vector constructor iterator' (0E21316h)
00E2850Amov edx,dword ptr
00E28510add edx,4
00E28513mov dword ptr ,edx
00E28519jmp main+245h (0E28525h)
00E2851Bmov dword ptr ,0
00E28525mov eax,dword ptr
00E2852Bmov dword ptr ,eax
00E28531mov dword ptr ,0FFFFFFFFh
00E28538mov ecx,dword ptr
00E2853Emov dword ptr ,ecx
delete []fPtr4;
00E28541mov eax,dword ptr
00E28544mov dword ptr ,eax
00E2854Amov ecx,dword ptr
00E28550mov dword ptr ,ecx
00E28556cmp dword ptr ,0
00E2855Dje main+294h (0E28574h)
00E2855Fpush 3
00E28561mov ecx,dword ptr
00E28567call MyClass::`vector deleting destructor' (0E2128Ah)
00E2856Cmov dword ptr ,eax
00E28572jmp main+29Eh (0E2857Eh)
00E28574mov dword ptr ,0
MyClass* fPtr5 = new( nothrow ) MyClass;
00E2857Epush 0E30228h
00E28583push 0Ch
00E28585call operator new[] (0E21311h)
00E2858Aadd esp,8
00E2858Dmov dword ptr ,eax
00E28593mov dword ptr ,4
00E2859Acmp dword ptr ,0
00E285A1je main+2FDh (0E285DDh)
00E285A3mov eax,dword ptr
00E285A9mov dword ptr ,2
00E285AFpush 0E21023h
00E285B4push 0E210BEh
00E285B9push 2
00E285BBpush 4
00E285BDmov ecx,dword ptr
00E285C3add ecx,4
00E285C6push ecx
00E285C7call `eh vector constructor iterator' (0E21316h)
00E285CCmov edx,dword ptr
00E285D2add edx,4
00E285D5mov dword ptr ,edx
00E285DBjmp main+307h (0E285E7h)
00E285DDmov dword ptr ,0
00E285E7mov eax,dword ptr
00E285EDmov dword ptr ,eax
00E285F3mov dword ptr ,0FFFFFFFFh
00E285FAmov ecx,dword ptr
00E28600mov dword ptr ,ecx
delete []fPtr5;
00E28603mov eax,dword ptr
00E28606mov dword ptr ,eax
00E2860Cmov ecx,dword ptr
00E28612mov dword ptr ,ecx
00E28618cmp dword ptr ,0
00E2861Fje main+356h (0E28636h)
00E28621push 3
00E28623mov ecx,dword ptr
00E28629call MyClass::`vector deleting destructor' (0E2128Ah)
00E2862Emov dword ptr ,eax
00E28634jmp main+360h (0E28640h)
00E28636mov dword ptr ,0
char x2;
MyClass* fPtr6 = new ( &x2 ) MyClass;
00E28640mov eax,1
00E28645imul eax,eax,0
00E28648lea ecx,x2
00E2864Cpush ecx
00E2864Dpush 0Ch
00E2864Fcall operator new[] (0E21389h)
00E28654add esp,8
00E28657mov dword ptr ,eax
00E2865Dmov dword ptr ,5
00E28664cmp dword ptr ,0
00E2866Bje main+3C7h (0E286A7h)
00E2866Dmov edx,dword ptr
00E28673mov dword ptr ,2
00E28679push 0E21023h
00E2867Epush 0E210BEh
00E28683push 2
00E28685push 4
00E28687mov eax,dword ptr
00E2868Dadd eax,4
00E28690push eax
00E28691call `eh vector constructor iterator' (0E21316h)
00E28696mov ecx,dword ptr
00E2869Cadd ecx,4
00E2869Fmov dword ptr ,ecx
00E286A5jmp main+3D1h (0E286B1h)
00E286A7mov dword ptr ,0
00E286B1mov edx,dword ptr
00E286B7mov dword ptr ,edx
00E286BDmov dword ptr ,0FFFFFFFFh
00E286C4mov eax,dword ptr
00E286CAmov dword ptr ,eax
fPtr6.~MyClass();
00E286CDpush 0
00E286CFmov ecx,4
00E286D4shl ecx,0
00E286D7add ecx,dword ptr
00E286DAcall MyClass::`scalar deleting destructor' (0E2101Eh)
fPtr6.~MyClass();//按照和构造顺序相反顺序析构
00E286DFpush 0
00E286E1mov ecx,4
00E286E6imul ecx,ecx,0
00E286E9add ecx,dword ptr
00E286ECcall MyClass::`scalar deleting destructor' (0E2101Eh)
}
00E286F1xor eax,eax
}
00E286F3push edx
00E286F4mov ecx,ebp
00E286F6push eax
00E286F7lea edx,ds:
00E286FDcall @_RTC_CheckStackVars@8 (0E21145h)
00E28702pop eax
00E28703pop edx
00E28704mov ecx,dword ptr
00E28707mov dword ptr fs:,ecx
00E2870Epop ecx
00E2870Fpop edi
00E28710pop esi
00E28711pop ebx
00E28712mov ecx,dword ptr
00E28715xor ecx,ebp
00E28717call @__security_check_cookie@4 (0E21050h)
00E2871Cadd esp,22Ch
00E28722cmp ebp,esp
00E28724call __RTC_CheckEsp (0E2135Ch)
00E28729mov esp,ebp
00E2872Bpop ebp
00E2872Cret
第一种形式为常规new,MyClass* fPtr1 = new MyClass;实现方式:
单步步入后IDE定位到new.cpp的
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{ // report no memory
_THROW_NCEE(_XSTD bad_alloc, );
}
return (p);
}
而步过之后直接跳转到构造函数中。
从上面new函数实现可以看到是使用malloc函数进行分配,如果失败则调用_callnewh调用注册过的“new操作符失败回调”函数(注册用_set_new_handler),如果原先没注册new失败回调,则抛出bad_alloc异常,可见在默认情况下,该while只会执行1次,仅当自定义new失败回调函数返回true,才可能多次尝试分配。
该种形式delete实现方式,单步以后vs不能定位到源码,不过我们可以换一种思路,既然知道一定执行析构函数,那么就在析构中下断点,断下后查看反汇编,并执行到上一级调用即可找到delete实现方法,因此看汇编实现,发现是一个名为“scalar deleting destructor”的内部函数:
00EB3470push ebp
00EB3471mov ebp,esp
00EB3473sub esp,0CCh
00EB3479push ebx
00EB347Apush esi
00EB347Bpush edi
00EB347Cpush ecx
00EB347Dlea edi,
00EB3483mov ecx,33h
00EB3488mov eax,0CCCCCCCCh
00EB348Drep stos dword ptr es:
00EB348Fpop ecx//以上部分为debug版API常见头,无需理会
00EB3490mov dword ptr ,ecx
00EB3493mov ecx,dword ptr
00EB3496call MyClass::~MyClass (0EB1023h)//执行析构
00EB349Bmov eax,dword ptr
00EB349Eand eax,1
00EB34A1je MyClass::`scalar deleting destructor'+3Fh (0EB34AFh)//如果传入参数允许释放则进行调用对应delete函数(对于定位new对应的delete该参数是设置为不允许的)
00EB34A3mov eax,dword ptr
00EB34A6push eax
00EB34A7call operator delete (0EB1154h)
00EB34ACadd esp,4
00EB34AFmov eax,dword ptr //以下是无关的收尾工作
00EB34B2pop edi
00EB34B3pop esi
00EB34B4pop ebx
00EB34B5add esp,0CCh
00EB34BBcmp ebp,esp
00EB34BDcall __RTC_CheckEsp (0EB1352h)
00EB34C2mov esp,ebp
00EB34C4pop ebp
00EB34C5ret 4
执行到call operator delete这行,步入之后转到源码,可以看到使用的是dbgdel.cpp的delete函数。实现如下:
void operator delete( void *pUserData )
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK);/* 阻塞其他线程*/
__TRY
/* 得到用于内存块信息头指针*/
pHead = pHdr(pUserData);
/* 检查区块类型 */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );//调用free函数释放内存
__FINALLY
_munlock(_HEAP_LOCK);/* 解锁其他线程*/
__END_TRY_FINALLY
return;
}
第二种方式为不抛出异常的new,MyClass* fPtr2 = new( nothrow ) MyClass;实现方式:
单步到newopnt.cpp中,
void * __CRTDECL operator new(size_t count, const std::nothrow_t&) _THROW0()
{ // try to allocate count bytes
void *p;
_TRY_BEGIN
p = operator new(count);
_CATCH_ALL
p = 0;
_CATCH_END
return (p);
}
#define _TRY_BEGIN try {
#define _CATCH(x) } catch (x) {
#define _CATCH_ALL } catch (...) {
#define _CATCH_END }
可见,这里用try块捕获了异常,因此不再抛出异常,余下的就是调用常规new函数而已。
第三种方式为定位new,
char x1;
MyClass* fPtr3 = new( &x1 ) MyClass;
这里所谓的定位就是说告诉new我们已经有一个内存位置了:
单步后发现new位于new文件:
inline void *__CRTDECL operator new(size_t, void *_Where) _THROW0()
{ // construct array with placement at _Where
return (_Where);
}
可以发现该new什么都没做,那么为什么还要new呢?仔细想想可以知道,编译器对new的处理是调用new函数后,之后将该地址作为this指针进行初始化操作(比如设置虚表),再调用构造函数,而构造函数这玩意不能直接调用,不像析构函数那样,因为构造之前还没有对象和指针呢,对象和指针是构造以后才有的,而调用析构函数的时候,是已经有对象或指针的。所以这种定位new在我理解,就是可以相当于可以直接构造了。
从上面可以看到new操作符先调用合适的new函数分配空间,之后调用构造函数构造,而delete函数刚好相对,先进行析构之后调用析构函数析构;同时可以看到布局new操作符的好处是可以手动指定构造和析构的时间,对于new无论哪种形式,在调用new函数分配好内存后都会调用构造函数进行构造,而定位new函数实则是直接返回,这就导致直接使用当前地址进行构造,相当于显示调用构造函数,而析构时由于没有实际分配空间,因此不能用delete,而是显示调用析构函数进行析构。
上面都是对于有构造函数和析构函数对象的情况,用delete时,编译器会为该类专门生成一个scalar deleting destructor函数,该函数中先进行析构,之后调用operator delete函数。当然,如果没有析构函数,那么就不会有scalar deleting destructor函数了,此时单步是可以看到delete源码的,即dbgdel.cpp中的void operator delete(void *pUserData)函数。这一点在delete用于基本类型时显而易见。
下面再来看数组的情况
第一种类型new,MyClass* fPtr4 = new MyClass,单步以后定位到newaop.cpp中:
void *__CRTDECL operator new[](size_t count) _THROW1(std::bad_alloc)
{ // try to allocate count bytes for an array
return (operator new(count));
}
而编译器传给该new[]函数的参数count是sizeof(MyClass)+sizeof(int) 该sizeof(int)用于内存管理
new[]()仍然调用了new();没有本质区别,即这么多对象占用的内存是当作整体分配的。再分配好之后,就需要对每个对象this指针处进行初始化和构造了。
这些都是边研究边写的,剩下2种情况下次讲述 赶上直播了 赞一个{:soso_e113:}
页:
[1]