zoukankan      html  css  js  c++  java
  • 从汇编看c++的new和delete

    下面是c++源码:

    class X {
    private:
        int _x;
    public:
        X(int xx = 0) : _x(xx) {}
        ~X() {}
    };
    
    
    int main() {
        X* xp = new X;
        delete xp;
      
    }

    代码很简单,在main函数里面先用new构造一个堆对象,然后用delelte释放此对象。

    接下来看构造堆对象的汇编码:

      :     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的功能。之所以要用代理函数,是因为某些情况下要释放的对象不止一个。

    下面是析构代理函数汇编码:

    ??_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 传回第一个堆对象首地址,而不是申请到的堆空间首地址

    从上面汇编码中还可以看到,申请到的堆空间首地址用来存放的是对象的个数,因此,堆空间总大小并不是对象大小 * 对象个数

    下面是构造代理函数的汇编码,只看相关部分:

    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[]可以发现,两种情况最主要的差别是在用最后用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++代码中ip1指向的是堆中基本类型int数组的首地址,ip1指向堆中单个基本类型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传参时分配的栈空间

    从汇编码可以看到,由于基本数据类型没有构造函数和析构函数,因此,这两种情况都只是简单的调用new分配空间,调用delete释放空间。并且还可以看到,和堆中对象数组不同,堆中基本类型数组没有在申请到的堆空间首地址处存放对象个数,ip1,ip2都直接指向的是各自申请到的堆空间首地址,正因为如此,对于基本类型,delete和delete[]效果一样。

  • 相关阅读:
    python_levenshtein 的安装和使用
    接口测试困难
    pycharm修改windows的IP
    Excel读取,修改,新建
    appium混合应用的处理
    冒泡排序
    选择排序
    插入排序
    python中两种退出方式os._exit(),sys.exit()
    二分查找
  • 原文地址:https://www.cnblogs.com/chaoguo1234/p/3213532.html
Copyright © 2011-2022 走看看