アセンブリから見るc++のnewとdelete

44804 ワード

次はc++ソースです.
class X {

private:

    int _x;

public:

    X(int xx = 0) : _x(xx) {}

    ~X() {}

};





int main() {

    X* xp = new X;

    delete xp;

  

}

コードは簡単で、main関数でnewでスタックオブジェクトを構築し、deelteでオブジェクトを解放します.
次に、構築スタックオブジェクトのアセンブリコードを見てみましょう.
  :     X* xp = new X;



    push    4;       4byte,   operator new      

    call    ??2@YAPAXI@Z                ;   operator new  

    add    esp, 4;operator new    ,      4byte,   operator new         

    mov    DWORD PTR $T2579[ebp], eax;   eax              ,      ST2579

    cmp    DWORD PTR $T2579[ebp], 0;     ST2579   0  ,               NULL

    je    SHORT $LN3@main;        ,      $LN3@main   ,      。       

    push    0; 0  ,           

    mov    ecx, DWORD PTR $T2579[ebp];     ST2579  (              )    ecx,             

                                  ;        this  

    call    ??0X@@QAE@H@Z                ;          

    mov    DWORD PTR tv70[ebp], eax;        ,   eax           ,           tv70

    jmp    SHORT $LN4@main;     $LN4@main   

$LN3@main:

    mov    DWORD PTR tv70[ebp], 0;         , 0     tv70

$LN4@main:

    mov    eax, DWORD PTR tv70[ebp]; tv70      eax

    mov    DWORD PTR _xp$[ebp], eax;    eax       xp

符号化から分かるように,newでスタックオブジェクトを構築する際の大まかな流れは,次のようになる.
1 operator new申請スタックスペースを呼び出す
2申請したスタックスペースの先頭アドレスを検査し、申請の失敗を防止し、空のポインタを返す
3スタック空間の申請が成功した場合、構造関数を呼び出し、スタック空間のヘッダアドレスをxpポインタに渡す.
従って,c++でnewでスタック空間を申請するのはmallocで申請するのとは異なり,前者はスタック空間が申請に成功したかどうかを自動的に検出する.
次に、プロファイル・スタック・オブジェクトの符号化を参照します.
 :     delete xp;



    mov    ecx, DWORD PTR _xp$[ebp]; xp    (       )    ecx

    mov    DWORD PTR $T2591[ebp], ecx; ecx       ST2591

    mov    edx, DWORD PTR $T2591[ebp];     ST2591      edx

    mov    DWORD PTR $T2590[ebp], edx;    edx         ST2590

    cmp    DWORD PTR $T2590[ebp], 0;     ST2590   0  ,                   

    je    SHORT $LN5@main;      ,      $LN5@main   ,  ,    。      

    push    1;         1      3       0        ,        (        )
mov ecx, DWORD PTR $T2590[ebp]; ST2590 ecx, (this ) call ??_GX@@QAEPAXI@Z; mov DWORD PTR tv75[ebp], eax; , eax , eax tv75 jmp SHORT $LN1@main; $LN1@main $LN5@main: mov DWORD PTR tv75[ebp], 0; , tv75 0 $LN1@main:; main

アセンブリ符号化から,呼び出しdeleteはoperator newのように解析関数とoperator delete関数を直接呼び出すのではなく,operator deleteの機能を完了するために解析エージェント関数を呼び出すことが分かる.プロキシ関数を使用するのは、解放するオブジェクトが1つ以上ある場合があります.
次に、プロファイルエージェント関数の符号化を示します.
 
??_GX@@QAEPAXI@Z PROC                    ; X::`scalar deleting destructor', COMDAT

; _this$ = ecx

    push    ebp

    mov    ebp, esp

    push    ecx;   ecx            ,                    

    mov    DWORD PTR _this$[ebp], ecx; ecx               

    mov    ecx, DWORD PTR _this$[ebp];           ecx,             

    call    ??1X@@QAE@XZ                ;       

    mov    eax, DWORD PTR ___flags$[ebp];               ,      ,       eax

    and    eax, 1; eax  (      )   1            delete       
je SHORT $LN1@scalar; 0, $LN1@scalar , , , mov ecx, DWORD PTR _this$[ebp]; ecx push ecx; ecx , delete call ??3@YAXPAX@Z ; delete add esp, 4;delete , 4byte, delete $LN1@scalar: mov eax, DWORD PTR _this$[ebp]; eax
;
mov esp, ebp pop ebp ret 4 ??_GX@@QAEPAXI@Z ENDP

アセンブリ符号化から,プロファイルエージェント関数はまず真のプロファイル関数を呼び出し,delete関数を呼び出して申請したスタック空間を解放することが分かる.この中間には、オブジェクトタイプフラグによりoperator deleteを呼び出してスタック空間を解放するか否かを判断する判断プロセスがある.
上記の符号化によりdeleteの流れは大体次のように見えます.
1オブジェクトヘッダ値が空かどうかを検出する
2空でない場合は、プロファイルエージェント関数を呼び出します.そうでない場合は、プロファイルエージェント関数を呼び出しません.
3解析エージェント関数でオブジェクトの解析関数とdelete関数を呼び出します(オブジェクトフラグが0でない場合)
c++のdelete呼び出しプロセスから見ると、deleteはポインタ変数xpの値を自動的にゼロにすることはありません.したがって、後続のプログラムではxpポインタを使用しても実行可能である.ポインタ変数xpとxpが指すオブジェクトの最大の違いは、どの宣言が終了したかです.deleteを呼び出すと、xpが指すオブジェクトは解析され、非合法になりますが、アドレス自体は合法的なプログラム空間を表します.
オブジェクト配列
次にnewとdeleteがオブジェクト配列に適用される場合を見る.
c++ソースコードは以下の通りです.
class X {

private:

    int _x;

public:

    X(int xx = 0) : _x(xx) {}

    ~X() {}

};





int main() {

    X* xp = new X[2];

    delete [] xp;

  

}

上記のコードはnewでスタックに2つのオブジェクトを構築しdeleteで解放する.
次に、構造プロセスの符号化を見ます.
 
   X* xp = new X[2];

00F035BD  push        0Ch ;   operator new       ,           

                          ;        4byte,      12byte,           

                          ;      4byte        

00F035BF  call        operator new ;  operator new    

00F035C4  add         esp,4 ;operator new    ,      4byte,   operator new           

00F035C7  mov         dword ptr [ebp-0F8h],eax;   eax               

00F035CD  mov         dword ptr [ebp-4],0  ;

00F035D4  cmp         dword ptr [ebp-0F8h],0  ;           0  ,        

00F035DB  je          main+97h (0F03617h)  ;       ,   NULL,     0F03617h      ,           

00F035DD  mov         eax,dword ptr [ebp-0F8h]  ;           eax

00F035E3  mov         dword ptr [eax],2  ; 2            ,        4byte       

00F035E9  push        offset X::~X (0F011DBh) ;

00F035EE  push        offset X::`default constructor closure' ;         ,            

00F035F3  push        2  ;       ,          

00F035F5  push        4  ;       ,          

00F035F7  mov         ecx,dword ptr [ebp-0F8h] ;           ecx 

00F035FD  add         ecx,4  ;ecx     4          4byte,  ecx               

00F03600  push        ecx  ;  ecx,          

00F03601  call        `eh vector constructor iterator' (0F011E5h)  ;        

00F03606  mov         edx,dword ptr [ebp-0F8h]  ;           edx

00F0360C  add         edx,4  ;edx     4          4byte,  edx               

00F0360F  mov         dword ptr [ebp-10Ch],edx  

00F03615  jmp         main+0A1h (0F03621h)  ;    0F03621h   

00F03617  mov         dword ptr [ebp-10Ch],0  ;

00F03621  mov         eax,dword ptr [ebp-10Ch]  

00F03627  mov         dword ptr [ebp-104h],eax  

00F0362D  mov         dword ptr [ebp-4],0FFFFFFFFh  

00F03634  mov         ecx,dword ptr [ebp-104h]  

00F0363A  mov         dword ptr [ebp-14h],ecx  ;               xp

上記の符号化プロセスは、概ね次のとおりです.
1 operator newを呼び出してスタックスペースを割り当てる
2コンストラクションエージェント関数コンストラクションスタックオブジェクトを呼び出し、コンストラクションエージェント関数を呼び出すと、スタックを介して5つのパラメータが渡され、それぞれa)最初のスタックオブジェクトヘッダb)スタックオブジェクトサイズ
c)スタックオブジェクト個数d)コンストラクション関数アドレスe)コンストラクション関数アドレス
3第1のスタックオブジェクトの先頭アドレスを返します.申請されたスタックスペースの先頭アドレスではありません.
上記の符号化からも分かるように、申請されたスタック空間ヘッダアドレスは、オブジェクトの個数を格納するために使用されるので、スタック空間の合計サイズは、オブジェクトサイズ*オブジェクト個数ではない
以下は、エージェント関数を構築するための集計符号化です.関連する部分のみを参照してください.
00F03792  mov         dword ptr [ebp-20h],0  

00F03799  mov         dword ptr [ebp-4],0  

00F037A0  mov         dword ptr [ebp-1Ch],0  ;          0    for   ,        0

00F037A7  jmp         `eh vector constructor iterator'+52h (0F037B2h)  ;     0F037B2h   

00F037A9  mov         eax,dword ptr [ebp-1Ch]  ;          eax

00F037AC  add         eax,1  ;   eax     1,      1

00F037AF  mov         dword ptr [ebp-1Ch],eax  ;           

00F037B2  mov         ecx,dword ptr [ebp-1Ch] ;          ecx 

00F037B5  cmp         ecx,dword ptr [ebp+10h]; ecx      dword ptr [ebp+10h]       (    2)  ,           

00F037B8  jge         `eh vector constructor iterator'+6Bh (0F037CBh)  ;            2,      0F037CBh  ,       

00F037BA  mov         ecx,dword ptr [ebp+8]  ;                ecx,           

00F037BD  call        dword ptr [ebp+14h] ;       

00F037C0  mov         edx,dword ptr [ebp+8]  ;              edx

00F037C3  add         edx,dword ptr [ebp+0Ch] ;edx          ,     ,            

00F037C6  mov         dword ptr [ebp+8],edx ;                

00F037C9  jmp         `eh vector constructor iterator'+49h (0F037A9h);     0F037A9h   

;======================      ,           ,    ========================

プロキシ関数を構築する役割は、コンストラクション関数をループ呼び出し、配列内の各オブジェクトを順次構築することです.
次に、配列オブジェクトを解放するための集計コードを示します.
delete [] xp;

00A2363D  mov         eax,dword ptr [ebp-14h];                 eax

                                             ;     eax       

00A23640  mov         dword ptr [ebp-0E0h],eax  

00A23646  mov         ecx,dword ptr [ebp-0E0h]  

00A2364C  mov         dword ptr [ebp-0ECh],ecx  ;  eax  (            )  ebp-0ECh    

00A23652  cmp         dword ptr [ebp-0ECh],0  ;             

00A23659  je          main+0F0h (0A23670h) ;    ,    0A23670h   ,  ,             

00A2365B  push        3  ;          ,1     ,3       ,0         ,      

                         ;                  

00A2365D  mov         ecx,dword ptr [ebp-0ECh]  ;                 ecx,               

00A23663  call        X::`vector deleting destructor' (0A211EAh)  ;  vector deleting destructor  
00A23668 mov dword ptr [ebp-10Ch],eax 00A2366E jmp main+0FAh (0A2367Ah) 00A23670 mov dword ptr [ebp-10Ch],0 13: 14: }

 
vector deleting destructor関数の符号化:
X::`vector deleting destructor':

00A21B10  push        ebp  

00A21B11  mov         ebp,esp  

00A21B13  sub         esp,0CCh  

00A21B19  push        ebx  

00A21B1A  push        esi  

00A21B1B  push        edi  

00A21B1C  push        ecx  ;        ,ecx           ,         

                           ;            ,              ecx

00A21B1D  lea         edi,[ebp-0CCh]  

00A21B23  mov         ecx,33h  

00A21B28  mov         eax,0CCCCCCCCh  

00A21B2D  rep stos    dword ptr es:[edi]  

;==========================================           ====================

00A21B2F  pop         ecx  ;       (               )  ,     ecx,

00A21B30  mov         dword ptr [ebp-8],ecx  ;         ebp-8      

00A21B33  mov         eax,dword ptr [ebp+8]  ;               eax 

00A21B36  and         eax,2  ; eax     2  ,           (       0 1 3,    3      0)

00A21B39  je          X::`vector deleting destructor'+61h (0A21B71h);     0,         

                                                                    ;     0A21B71h                    

00A21B3B  push        offset X::~X (0A211DBh);

00A21B40  mov         eax,dword ptr [this] ;this                ,         eax

00A21B43  mov         ecx,dword ptr [eax-4]  ;                 4byte     (            

                                             ;    ,            )    ecx

00A21B46  push        ecx  ; ecx  ,             

00A21B47  push        4  ;

00A21B49  mov         edx,dword ptr [this]  ;                 edx

00A21B4C  push        edx ; edx    ,              

00A21B4D  call        `eh vector destructor iterator' (0A211F4h) ;         

00A21B52  mov         eax,dword ptr [ebp+8]  ;        ,               

00A21B55  and         eax,1;    eax   1  ,          delete         

00A21B58  je          X::`vector deleting destructor'+59h (0A21B69h)  ;       0,      0A21B69h   ,  

                                                                      ;

00A21B5A  mov         eax,dword ptr [this]  ;                 eax

00A21B5D  sub         eax,4  ; eax     4,    eax    ,  ,eax                

00A21B60  push        eax  ; eax    ,         operator delete

00A21B61  call        operator delete (0A21087h) ;  operator delete,      

00A21B66  add         esp,4  ;  operator delete  ,           

00A21B69  mov         eax,dword ptr [this];                 eax  

00A21B6C  sub         eax,4  ; eax   4,   eax    ,  ,eax                

00A21B6F  jmp         X::`vector deleting destructor'+80h (0A21B90h)  ;     0A21B90h   

;=====================               3      =====================

00A21B71  mov         ecx,dword ptr [this] ;        3,        。              ecx,             

00A21B74  call        X::~X (0A211DBh)  ;      

00A21B79  mov         eax,dword ptr [ebp+8] ;           eax 

00A21B7C  and         eax,1  ;     ,         

00A21B7F  je          X::`vector deleting destructor'+7Dh (0A21B8Dh)  ;       0,      0A21B8Dh   

                                                                      ;

00A21B81  mov         eax,dword ptr [this]  ;           eax

00A21B84  push        eax  ;  eax,  operator delete   

00A21B85  call        operator delete (0A21087h)  ;  operator delete

00A21B8A  add         esp,4  ;operator delete    ,              

00A21B8D  mov         eax,dword ptr [this] ;           eax,      

00A21B90  pop         edi  

00A21B91  pop         esi  

00A21B92  pop         ebx  

00A21B93  add         esp,0CCh  

00A21B99  cmp         ebp,esp  

00A21B9B  call        @ILT+305(__RTC_CheckEsp) (0A21136h)  

00A21BA0  mov         esp,ebp  

00A21BA2  pop         ebp  

00A21BA3  ret         4  

vector deleting destructorの全体的な流れも、まずプロファイルエージェント関数を呼び出し、operator delete解放空間(オブジェクトフラグが0でない場合)を呼び出し、プロファイルエージェント関数を呼び出すときも4つのパラメータaを呼び出す)スタック空間の最初のオブジェクトヘッダアドレスb)対象サイズc)オブジェクト個数d)虚関数アドレス
次に、プロファイル・エージェント関数の集計符号化を示します.
mov         ecx,dword ptr [ebp+0Ch]  ;       ,    ecx

00A236E0  imul        ecx,dword ptr [ebp+10h]  ;ebp+10h              ,     ecx   ebp+10h

                                               ;    ,      ecx  

                                               ;ecx                 

00A236E4  add         ecx,dword ptr [ebp+8]  ;ebp+8                     ,     ecx  

                                             ;     ecx  ,  ecx                     

                                             ;                 

00A236E7  mov         dword ptr [ebp+8],ecx  ; ecx     ebp+8      

00A236EA  mov         dword ptr [ebp-4],0  

00A236F1  mov         edx,dword ptr [ebp+10h]  ;         edx,   for           

00A236F4  sub         edx,1  ;edx     1

00A236F7  mov         dword ptr [ebp+10h],edx ; edx       ebp+10h        

00A236FA  js          `eh vector destructor iterator'+6Dh (0A2370Dh) ;  edx-1   ,     0A2370Dh    00A236FC mov eax,dword ptr [ebp+8] ; ebp+8    (               )    eax 00A236FF sub eax,dword ptr [ebp+0Ch] ;ebp+0ch            ,   eax-     ;                        , ;      eax 00A23702 mov dword ptr [ebp+8],eax; eax     ebp+8       00A23705 mov ecx,dword ptr [ebp+8];        ecx   ,              00A23708 call dword ptr [ebp+14h] ;ebp+14h                  ,         00A2370B jmp `eh vector destructor iterator'+51h (0A236F1h);     0A236F1h   ;               

解析エージェント関数も、構築オブジェクトとは逆の順序でオブジェクトを解析するために、解析関数をループ呼び出していることがわかります.
単一のスタックオブジェクトに対してdeleteを呼び出す場合とスタックオブジェクト配列に対してdeleteを呼び出す場合とを比較すると、2つの場合の最も主要な違いは、最後にdeleteでスタック空間を解放する場合、delete[]がターゲットポインタ(すなわちdeleteにとって、ターゲットポインタはスタックオブジェクトヘッダアドレスであり、delete[]にとってスタック内の最初のスタックオブジェクトヘッダアドレスである)に対して4を減らす調整を行うことである.したがって,単一オブジェクトスタック空間を解放しdelete[]を誤って使用すると,中間検出を実行すると,オブジェクトタイプが3とマークされ,ターゲットポインタの調整が行われ,結果として誤ったスタック空間が解放される.同様に,オブジェクト配列を解放しdeletを誤って使用すると,中間検出を実行すると,オブジェクトタイプが1と判定され,ターゲットポインタ調整を行わずにスタック空間の解放にもエラーが発生する.
基本データ型
次はc++ソースです.
class X {

private:

    int _x;

public:

    X(int xx = 0) : _x(xx) {}

    virtual ~X() {}

};



class Y  {

private:

    int _y;

public:

    Y(int yy = 0) : _y(yy) {}

    virtual ~Y() {}

};



class Z : public X, public Y {

private:

    int _z;

public:

    Z(int zz = 0) : _z(zz){}

    virtual ~Z() {}

};







int main()  {

    int* ip1 = new int[2];

    delete [] ip1;



    int* ip2 = new int;

    delete ip2;

    

  

}

c++コードのip 1は、スタック内の基本タイプint配列のヘッダアドレスを指し、ip 1は、スタック内の単一の基本タイプintのヘッダアドレスを指す.
次はmian関数の符号化です.
 28:     int* ip1 = new int[2];

00141A3E  push        8  ;           ,       operator new

00141A40  call        operator new (141177h) ;  operator new 

00141A45  add         esp,4  ;     4      operator new         

00141A48  mov         dword ptr [ebp-104h],eax  ;   eax              ,  

                                                ;           

00141A4E  mov         eax,dword ptr [ebp-104h]  

00141A54  mov         dword ptr [ip1],eax  ;eax         ip1

    29:     delete [] ip1;

00141A57  mov         eax,dword ptr [ip1]  ; ip1  (        )    eax

00141A5A  mov         dword ptr [ebp-0F8h],eax  ;         

00141A60  mov         ecx,dword ptr [ebp-0F8h]  

00141A66  push        ecx  ;ecx           ,   ecx  ,   delete  

00141A67  call        operator delete (141082h)  ;  delete

00141A6C  add         esp,4  ;     4,     delete         

    30: 

    31:     int* ip2 = new int;

00141A6F  push        4 ;           ,       operator new

00141A71  call        operator new (141177h);  operator new  

00141A76  add         esp,4  ;     4      operator new         

00141A79  mov         dword ptr [ebp-0ECh],eax  ;   eax              ,  

                                                ;           

00141A7F  mov         eax,dword ptr [ebp-0ECh]  

00141A85  mov         dword ptr [ip2],eax  ;eax       ip2

    32:     delete ip2;

00141A88  mov         eax,dword ptr [ip2]  ; ip2  (        )    eax

00141A8B  mov         dword ptr [ebp-0E0h],eax   ;         

00141A91  mov         ecx,dword ptr [ebp-0E0h]  

00141A97  push        ecx  ;ecx           ,   ecx  ,   delete  

00141A98  call        operator delete (141082h);  delete  

00141A9D  add         esp,4 ;     4,     delete         

符号化から,基本データ型には構造関数と構造関数がないため,この2つの場合は単純にnew割り当て空間を呼び出しdelete解放空間を呼び出すだけであることがわかる.また、スタック内のオブジェクト配列とは異なり、スタック内の基本タイプ配列が申請されたスタック空間ヘッダにオブジェクト個数を格納していないことも見られ、ip 1,ip 2はそれぞれ申請されたスタック空間ヘッダを直接指している.そのため、基本タイプではdeleteとdelete[]の効果は同じである.