C++的malloc和new的区别

1.对于C++而言,malloc是函数,但是new是运算符

看似函数和运算符实现的功能都差不多。但是对于C++来说,new是运算符就意味着我们可以进行运算符重载,这就意味着我们可以定制我们自己的new内存分配器。
同时,由于C++特有的异常处理机制,我们不但可以在我们内存分配失败的时候,new返回一个null,同时也可以报出一个bad_alloc错误,同时调用我们的new_handler(new运算符错误处理程序),但是我们的new_handler应该如何写呢。
还是先来一段代码:

class NewHandlerHolder
{
public:
    explicit NewHandlerHolder(std::new_handler nh):handler(nh)  //取得目前的new-handler.释放它
    ~NewHandlerHolder()
    {std::set_new_handler(handler); }
private:
    std::new_handler handler;           //记录下来,阻止copying
    NewHandlerHolder(const NewHandlerHolder&);  
    NewHandlerHolder& operator=(const NewHandlerHolder&);
};

void* Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
    NewHandlerHolder h(std::set_new_handler(currentHandler));   //安装Widget的new-handler.
    return ::operator new(size);                    //分配内存或抛出异常.恢复global new-handler.
}

template<typename T>    //"mixin"风格的base class,用以支持
//class 专属的set_new_handler
class NewHandlerSupport
{
public:
    static std::new_handler set_new_handler(std::new_handler p)throw();
    static void* operator new(std::size_t size) throw(std::bad_alloc);
private:
    static std::new_handler currentHandler;
};

template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw()
{
    std::new_handler oldHandler=currentHandler;
    currentHandler=p;
    return oldHandler;
}
template<typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size) throw(std::bad_alloc)
{
    NewHandlerHolder h(std::set_new_handler(currentHandler));
    return ::operator new(size);
}
//以下将每一个currentHandler初始化为null
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler=0;

上面的代码,将我们重载的New以及new_handler函数封装成了一个类使用。但是,这里要注意的是,如果我们多重继承这个NewHandler类,要看适不适合我们的类,否则将会出现错误。

2.对于C++而言,new运算符可以动态申请我们的一个对象的空间

这里,我还是以一个例子来说明new来动态申请我们的一个对象的空间会引发什么问题:

Example* em=new Example

我们申请一个Example类的对象的时候,一共做了两件事:
1.类似malloc的过程申请了一块空间。
2.调用Example类的构造函数。
但是如果在第二步的时候,程序抛出了异常。那么是不是代表着new的过程失败了,抛出一异常,返回了null。第一步申请的空间还是申请,但没有得到指针进行delete操作,这就造成了内存泄露。
这明显是我们不想看到的不可控的情况。
实际上解决这个问题的方法也有两种:
1.对内存块进行签名。
2.我们每一次new的过程中都要返回一个指向空间的指针就行了。
实际上,我们的C++的new库已经为我们实现了这个功能:

#include<new>
void* operator new(std::size_t,void* pMemory) throw()

那个pMemory就是指向我们空间的指针啦,这样即使出现刚刚的情况,我们也可以利用这个指针来进行返回。
还有一种是基于签名的方式,这里还是直接上代码:

static const int signature=0xDEADBEEF;
typedef unsigned char Byte;
void* operator new(std::size_t size) throw(std::bad_alloc)
{
    using namespace std;
    size_t realSize=size+2*sizeof(int);     //增加大小,使能够塞入两个signatures.
    void* pMem=malloc(realSize);            //调用malloc取得内存。
    if(!pMem) throw bad_alloc();

    //将signature写入内存的最前段落和最后段落
    //减一个int是为了能够写在这个int的地方写上签名
    *(static_case<int*>(pMem))=signature;
    *(reinterpret_case<int*>(static_case<Byte*>(pMem)+realSize-sizeof(int)))=signature;

    //返回指针,指向恰位于第一个signature之后的内存位置.
    return static_cast<Byte*>(pMem)+sizeof(int);
}

可以从代码看出,主要做的改动就是把原来申请的空间扩展了两个int的签名,然后头尾加上签名,之后返回的是第一个签名后的内存位置。
但是这样做可能无法实现的内存对齐的要求(内存对齐是为了更快的访问(直接通过内存的移位操作进行访问))。
既然提到了内存对齐的问题,这里就要提一下一个优秀的内存分配器应该考虑什么,我们考虑个性化的内存分配器应该考虑什么?

3.优秀的内存分配器应该如何设计考虑

内存分配器的具体设计在我的另一篇博客上有提到:
http://blog.csdn.net/github_33873969/article/details/78571868
这里我们更专注内存分配器的其他方面:
1.线程安全(在多线程环境下,我们的new过程还是否安全)
2.文件系统的内存对齐问题
3.对于使用内存情况的统计问题
4.为了将相关对象成簇集中
这里我们简单的提几点
1.为了保证线程安全的时候,我们在new的时候如果对链表进行操作需要对链表的结点利用锁机制进行保护。但是这样就会降低一定量的效果。这就给我们定制new做了一个要求,要在只有单线程的环境下把相关锁关掉。
3.关于使用内存情况的统计问题,我们可以在我们的new的过程中,统计我们申请的块的大小情况,回收与申请的时间间隔如何,他们更倾向于FIFO次序或LIFO(后进先出)次序,之后我们可以再进行合理化定制。
4.为了将相关对象成簇集中。为了考虑局部性原理,我们可以将相同的对象放在一起,为了能够得到cache亲和,来进行快速访问。
Reference:Effective C++ (改善程序与设计的55个具体做法)

猜你喜欢

转载自blog.csdn.net/github_33873969/article/details/79614399
今日推荐