实用经验 21 尽量用new/delete替代malloc/free

这篇博客探讨了C++中的new/delete与C语言的malloc/free的区别。new/delete不仅是内存管理工具,还能调用构造函数和析构函数,而malloc/free仅分配和释放内存。new/delete是C++的运算符,malloc/free是C语言函数。new有三种形态:new operator、operator new和placement new,分别涉及内存申请、构造函数调用和对象定位构造。建议C++程序员使用new/delete以利用其类型安全和对象管理特性,避免混合使用malloc/free。

malloc/free 是C语言中内存申请和释放函数,利用他们我们可方便的管理内存,而在C++中我们又有了新的工具new/delete。我想你可能会迷惑:有了malloc/free为什么还要new/delete呢?malloc/free与new/delete到底有什么区别,我们看下面的代码:

//  Obj.h 文件   声明CObj类
#include <iostream>

class CObj
{
public:
    CObj(void);
    virtual ~CObj(void);
    void   Hello();
};

CObj::CObj(void)
{
    cout << "CObj is born!" << endl;
}

//  Obj.h 文件   实现CObj类
CObj::~CObj(void)
{
    cout << "CObj is died!" << endl;
}

void CObj::Hello()
{
    cout << "Hello I'm the CObj!" << endl;
}

// main.cpp 测试程序
int _tmain(int argc, char* argv[])
{
    cout << "Using malloc 和 Free....." << endl;
    CObj *pObjA = (CObj *)malloc(sizeof(CObj));
    pObjA->Hello();
    free(pObjA);

    cout << endl;
    cout << "Using new 和delete....." << endl;
    CObj *pObjB = new CObj;
    pObjB->Hello();
    delete pObjB;
}

上述代码段的执行结果是:

Using malloc 和 Free.....
Hello I'm the CObj!

Using new 和 delete.....
CObj is born!
Hello I'm the CObj!
CObj is died!

通过结果我们可以知道:new/delete在管理内存的同时会调用类的构造函数和析构函数。而malloc/free仅仅实现了内存的分配和释放,没有调用类的构造函数和析构函数。

malloc/free是C/C++语言的标准库函数,而new/delete是C++语言的运算符关键字。由于malloc/free是C/C++语言的标准库函数,所以在使用时需要头文件库函数支持。对于非内置数据类型,用malloc/free无法完成动态对象的创建要求。这是因为对象的创建时会自动的调用构造函数,释放时会调用析构函数。由于malloc/free不是运算符,不受编译器控制。所以无法完成对象创建和释放时构造函数和析构函数的调用。所以我们可以打这么一个比方:通过new创建一个对象,相当于盖一座房子,而malloc相当于申请一块地皮,要想成为房子还需要很多的努力。

void *malloc(long NumBytes);

该函数分配NumBytes个字节,并返回了指向这块内存的指针。如果分配失败,则返回一个空指针(NULL)。关于分配失败的原因,应该有多种,比如说空间不足就是一种。

void free(void *FirstByte);

该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。

小心陷阱

  • 申请了内存空间后,必须检查是否分配成功;当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。
  • malloc/free应该是配对的。如果申请后不释放就会内存泄露;释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。
  • 虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。

new/delete为运算符,使用语法如下:

指针名 = new 类型(参数);		// 单个对象内存申请
指针名 = new 类型[个数];		// 多个对象内存申请
delete  指针名;       		// 释放单个对象内存
delete[] 指针名;       		// 释放多个对象内存

小心陷阱

  • new/delete应该配对使用。如果用new申请了内存,在不使用时记得通过delete将申请的内存归还系统。
  • new申请内存时,返回的类型就是需要的数据类型。不需要像malloc那样进行一次强制转换。

通过上述的分析,我们可对new/delete和malloc/free区别总结如下:

  • new/delete为运算符,而malloc/free为C语言标准库函数。
  • 通过new创建的对象具有类型,而malloc的返回值为void*,需要进行强制类型转换。
  • new在创建类型时会自动调用对象的构造函数完成对象的初始化工作,而malloc函数不会。执行delete时会自动调用对象的析构函数,而free则不会。
  • new申请内存失败时,会调用new_handler处理函数。而malloc申请内存失败时仅仅返回NULL。不会进行任何的善后处理。

下面我们主要讨论一下new/delete,众所周知new运算符可完成内存的申请和构造函数的初始化调用。如你认为这是new的全部,那你就错了。我不得不说,你被new伪装的外部迷惑了。的确作为new操作符时,new确实表现出了上述行为。其实new有三种不同的形态。他们是new operator、oprerator new和placement new。上述我们所讨论的只是其中一种形态:new operator。下面我们继续讨论其他两种,并给出他们的对比。

new operator的执行过程可分为三步,如图3-1所示:
(1)通过operator new 申请内存;
(2)使用placement new 调用构造函数(简单类型忽略此步);
(3)返回内存指针。如图3-1所示。可以看出new operator与其他两种形态有着密切的关系。

在这里插入图片描述

图3-1 new operator执行过程

operator new在C++中有着与加减乘除一样的性质,C++库有标准的默认实现形式。operator new的声明如下:

void *operator new(size_t size);

operator new在默认情况下首先调用malloc实现内存的分配,如果分配成功则直接返回,如果分配失败则调用new_handler,然后重复前面的过程,直到抛出异常为止。

operator new函数的返回值为void *。值得说明的是此函数返回的是一个未经处理的指针。如果你对默认的operator new函数不是很满意,你可通过重载operator new实现满足要求的operator new函数。

operator new函数的重载,如下例所示:

class CObj
{
public:
    CObj(void);
    virtual ~CObj(void);
    void *operator new(size_t size);
};

void *CObj::operator new(size_t size)
{
    cout << "this is my new ....." << endl;
    void *p = NULL;
    while ((p = malloc(size)) == 0)
    {   
    	static const std::bad_alloc nomem; // report no memory
        _RAISE(nomem);
    }
    return (p);
}

小心陷阱 重载operator函数时,需注意

  • 如果你的operator new函数调用了全局的operator new函数时,需保持高度警惕,因为全局的operator new函数亦是可重载的。
  • 像new/delete操作符一一对应一样,operator new 和operatordelete也是一一对应的。如果重载了operator new,那么也需重载对应的operator delete。

最后,我们介绍new的第三种形态placement new。placement new可实现对象的定位构造。通过它可以实现类对象的指定构造函数的调用。具体使用可参考下例。

#include "Obj.h"
#include <new>
using namespace std;

int main()
{
    void *p = ::operator new (sizeof(CObj));	// 申请CObj所需的内存空间,但不进行初始化。
    CObj *pObj = static_cast<CObj *>(p);
    new(pObj)  CObj();   			// 调用类的构造函数,初始化分配的内存
}

placement new是标准C++库的一部分,声明在中。所以如果你使用placement new时需要包含或<new.h>。placemnet new的声明如下:

#ifndef __PLACEMENT_NEW_INLINE
#define __PLACEMENT_NEW_INLINE
inline void *__CRTDECL operator new(size_t, void *_Where)
        {return (_Where); }
inline void __CRTDECL operator delete(void *void *)
        {return; }
#endif

现在我们说一下new(pObj) CObj();这种奇怪的调用形式。这种调用功能:在特定的内存上调用特定的构造函数实现一个对象的构造。STL的allocator就采用placement new实现内存的管理。通过这种方式实现内存的管理具有更灵活的优点。当然,如果你通过placment new 实现对象构造函数的调用,你也必须显示的调用与之对应的placment delete:pObj->~CObj();。

通过上述的讨论我们可以得到下面的结论:

  • 从功能上说,new/delete具备malloc/free的所有功能,而且远远超过malloc/free。同时还具有更好的安全特性。
  • C++中保留malloc/free,目的是实现与C语言的兼容。所以作为C++的程序员,我们在编程过程中应该多使用new/delete而尽量避免使用malloc/free。
  • 同样new/delete和malloc/free混合使用更是一个不明智的选择。在使用时推荐使用new/delete,尽量减少malloc/free的使用。

请谨记

  • 不论new/delete还是malloc/free,使用时必须配对使用。如果将混淆了他们那将是一件蠢事。
  • 不要企图用malloc/free完成动态对象的内存管理,应该使用new/delete。
  • 明确区分new/delete的三种形态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值