从汇编看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[]效果一样。

 

 

 

 

 

posted @ 2013-07-27 01:54  chaoguo1234  阅读(1999)  评论(2编辑  收藏  举报