元始天尊 发表于 2014-10-12 17:42:11

重识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种情况下次讲述

0xAA55 发表于 2014-10-12 17:47:31

赶上直播了

井底观月 发表于 2014-10-12 20:00:33

赞一个{:soso_e113:}
页: [1]
查看完整版本: 重识new之二