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

### C++ 中 `malloc`、`realloc` `new` 的区别及使用方法 #### 动态内存分配概述 动态内存分配允许程序在运行时请求额外的存储空间。C++ 支持多种方式来实现这一点,其中最常用的是通过标准库函数 `malloc`/`realloc` 以及运算符 `new`。 #### 函数声明与头文件引入 为了使用这些功能,在源代码顶部需包含相应的头文件: 对于 C 风格的内存管理操作 (`malloc`, `realloc`) ,应加入 `<cstdlib>` 或者更传统的 `<stdlib.h>` 。而针对面向对象特性的 `new` 运算符,则不需要显式导入任何特定头部单元,因为这是内置支持的一部分[^2]。 #### Malloc 使用说明 `malloc()` 是用于从堆上分配指定字节数量未初始化的空间,并返回指向该区域起始位置的一个通用指针(即 `void*`)。由于其不执行构造函数调用,因此仅适用于基本数据类型的简单数组创建场景。需要注意的是,当不再需要这块资源时务必记得调用 `free()` 来回收它所占用的位置,防止发生泄漏问题[^1]。 ```cpp // 假设要为整数数组预留足够的容量 int n = 5; int *array = static_cast<int*>(malloc(n * sizeof(int))); if (!array) { // 错误处理... } for (int i = 0; i < n; ++i){ array[i]=i*i; } free(array); ``` #### Realloc 使用说明 如果先前已经利用 `malloc()` 获取了一片连续地址区间但后来发现不够用了怎么办?这时就可以借助于 `realloc()` 实现原地扩展或收缩的目的——传入旧有缓冲区首址加上新的期望大小作为参数即可完成调整动作;当然也有可能因种种原因无法满足需求从而返回 NULL 指向新分配出来的更大范围内的另一处空白地带供后续迁移过去继续沿用下去。 ```cpp // 尝试增加原有数组长度至两倍 n *= 2; array = static_cast<int*>(realloc(array, n * sizeof(int))); if (!array) { // 错误处理... } else { for (; old_n < n; ++old_n){ array[old_n]=(old_n-old_n%3)*7+9; } } ``` #### New 使用说明 不同于上述两者基于过程式的编程范式设计思路,现代 C++ 更推崇采用类成员形式的新建实例语法糖衣包裹下的底层机制运作原理:每当遇到像 T obj[] 这样的表达式时编译器便会自动帮我们合成一段隐含着适当规模申请逻辑在内的机器码片段并负责安排好一切必要的准备工作直至最终能够顺利产出预期的对象实体出来为止。更重要的一点在于每当我们运用此关键字构建复合结构体的时候还会顺带触发关联模板定义里预置好的默认构造子例程去填充各个字段初始状态值确保不会残留不确定因素影响正常使用体验。 ```cpp class MyClass {}; MyClass* pInstance = new MyClass(); // 单个对象 delete pInstance; pInstance = new MyClass[10]; // 数组版本 delete [] pInstance; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值