剖析VC6.0中为类对象申请内存的过程

本文详细探讨了VC6.0中使用new和delete操作符为类对象申请和释放内存的过程。通过实例代码和汇编分析,解释了new在申请类对象时会调用构造函数,delete会调用析构函数,而malloc/free不会涉及这些过程。此外,文章还解析了new[]和delete[]的区别,包括释放类对象数组时的构造和析构顺序,以及为何必须匹配使用new[]和delete[]。

摘要: 在用VC开发时常常需要对内存进行操作,其中C++中提供了运算符newdelete;而C语言库中提供有mallocfree。它们在具体使用时存在着一些差别,本文主要剖析一下VC6.0中为类对象申请内存的过程。方便大家理解在为类对象申请或释放内存时的一个问题。

    关键词:VC6.0,内存申请,C++newdelete

1.   总述

在用VC开发时常常需要对内存进行操作,其中C++中提供了运算符newdelete;而C语言库中提供有mallocfree。它们在具体使用时存在着一些差别,大家也可能一些疑问再里面,如:为什么C++运算符new再为类对象申请内存时会调用类的构造函数而malloc则不会,为什么用new申请一个对象数组时就必须用delete[]释放而申请一个long类型的数组就能用delete释放,new一个对像与new一个对象数组以及new一个基本数据类型的数组有什么不同,为什么用new申请一个对像用delete[]释放有时会出错而有时就成功出错是随机的吗等等;本文主要剖析一下VC中为类对象申请内存的过程同时解答上述几个疑问;希望能给大家予以一定帮助。这里说明一下试验的环境:VC6.0编译环境,IDE5.0。

2.   malloc VS new

这个两操作的区别主要是在申请一个类的对象时,malloc不会调用类对象的构造函数而new会调用,我们这里定义一个类用来实际测试,下面我们将以这个类为基础讨论mallocnew的差别。

long g_Number = 0;

class CMemoryItem 

{

public:

    CMemoryItem()

    {

        m_info.Format("Construct CMemoryItem Nember[%d] /n", ++g_Number);

        OutputDebugString(m_info);

    }

   

    virtual ~CMemoryItem()

    {

        m_info.Replace("Construct","Destructor");

        OutputDebugString(m_info);

        g_Number--;

    }

private:

    CString m_info;

};

    下面我看几段代码与它们的执行结果:

    代码1

    CMemoryItem* pItem = new CMemoryItem;

delete pItem;

 

    执行输出:

    Construct CMemoryItem Nember[1]

Destructor CMemoryItem Nember[1]

从这个输出中我们可以看到当new时同时调用了类的构造函数当调用delete时调用了类的析构函数那么我们再看看C语言中mallocfree的执行情况。

代码2

CMemoryItem* pItem = (CMemoryItem*)malloc(sizeof(CMemoryItem));

free(pItem);

执行输出:

这时我们没有看到有代码1那样的输出,可见malloc时并没有去调用类的构造函数,free时也没有调用类的析构函数;这样我们就验证的了newdelete在为类申请内存时会调用类的构造与析构函数,而mallocfree不会。下面我们一起看看它们汇编代码是什么样一个情况,首先来看代码1片段:

00401D76   push        edx ;

00401D77   push        offset THIS_FILE (00416690)

00401D7C   push        8

00401D7E   call        operator new (004021c4)

00401D83   add         esp,0Ch

00401D86   mov         dword ptr [ebp-28h],eax

00401D89   mov         dword ptr [ebp-4],0

00401D90   cmp         dword ptr [ebp-28h],0

00401D94   je          CTestMemAllocateDlg::OnDelete+93h (00401da3)

00401D96   mov         ecx,dword ptr [ebp-28h]

00401D99   call        @ILT+15(CMemoryItem::CMemoryItem) (00401014)

00401D9E   mov         dword ptr [ebp-38h],eax

        其中edx里存放的是CMemoryItem* pItem = new CMemoryItem;这句代码在CPP文件中的行号,offset THIS_FILE (00416690)则是CPP文件的路径(因为我们调试的是DEBUG版本所以会有这个两个参数,大家可以用IDE反译一下RELEASE版本就能看到是没有这两个参数的),8就是这个类的长度,通过对sizeof(CMemoryItem)的输出可以证实这类的长度是8,至于为什么找度会是8我们在这里不做深入的讨论,接下来就是对new的一个调用。当调用完成后对调用的反回值做一是否为0的一个验证;紧接着就调用了CMemoryItem类的构造函数,构造函数会再返回这个对象的起始内存地址。

让我们再来看一下用malloc申请内存的汇编代码,以加深理解两者的区别。下面的malloc申请内存的汇编代码:

       

00401D2D   mov         esi,esp

00401D2F   push        8

00401D31   call        dword ptr [__imp__malloc (004176c8)]

00401D37   add         esp,4

00401D3A   cmp         esi,esp

00401D3C   call        _chkesp (004021ca)

00401D41   mov         dword ptr [ebp-8],eax

00401D44   mov         esi,esp

00401D46   mov         eax,dword ptr [ebp-8]

00401D49   push        eax

00401D4A   call        dword ptr [__imp__free (004176cc)]

00401D50   add         esp,4

00401D53   cmp         esi,esp

00401D55   call        _chkesp (004021ca)

大家可以看到这里除对堆栈做了两次较验外我们没有看到任何有关构造和析构函数的调用;从以上分析可以看出两者的区别就在于:用new申请类对象时编译器加入对类构造函数的调用而malloc则没有,这是因为mallocC语言中的函数在C语言中没有类的概念所实现时也不会为其加入对构造函数的调用,如果我们单步调试到00401D7E   call        operator new (004021c4)时,按F11进入它的内部就能看到它的内部仍然是把调用转给了malloc函数(这里只针对未在类中重载new操作而言是这样,如果类中重载了new操作符这句则是对新重载函数的调用);free delete区别也类似是这样的。

3.   剖析new操作的过程

这节一节我们来详细看一下申请一个类对象时的构造过程和申请一个类对象数组时的构造过程以及为基本数据类型申请数组时的一些情况并对比三者的不同;试验时用的类还是上面的CMemoryItem类。

3.1. NEW单个类对象的过程

3.1.1. 类中有虚函数的情况

    在第二节中我们了解new一个类对象过程但并未提及它的构造过程,这里我们接着上面的内容剖析一下单个类的构造过程。看下面的示例代码:

代码1

  CMemoryItem* pItem = new CMemoryItem;

delete pItem;

在上一节我们已经了解这个类对象new的过程,这里我们不再重复而是要着重看一下在这个类的构造函数里做了哪些操作。在调用类的构造函数时,代码段是这样的:

00401D96   mov         ecx,dword ptr [ebp-28h]

00401D99   call        @ILT+15(CMemoryItem::CMemoryItem) (00401014)

00401D9E   mov         dword ptr [ebp-38h],eax

dword ptr [ebp-28h]中存放着为CMemoryItem类对象申请的内存起始地址,我们现在跟踪一下类的构造函数来看看内部的执行流程,看现在的汇编代码:

00401DF0   push        ebp

00401DF1   mov         ebp,esp

00401DF3   push        0FFh

00401DF5   push        offset __ehhandler$??0CMemoryItem@@QAE@XZ (004037ec)

00401DFA   mov         eax,fs:[00000000]

00401E00   push        eax

00401E01   mov         dword ptr fs:[0],esp

00401E08   sub         esp,44h

00401E0B   push        ebx

00401E0C   push        esi

00401E0D   push        edi

00401E0E   push        ecx

00401E0F

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值