new和malloc的区别、ptmalloc

一、new和malloc的区别

1、new/delete是C++的运算符/关键字,malloc与free是c++/c语言的标准函数

void* malloc(size_t);
void free(void*);
void *operator new (size_t);
void operator delete (void *);
void *operator new[] (size_t);
void operator delete[] (void *);

2、new开辟的内存是自由存储区,malloc堆上开辟

自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。

那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。

3、new 还可以做初始化,malloc单纯开辟内存

int* buffer = new int(); // 分配的一个int初始化为0
int* buffer = new int(0); // 分配的一个int初始化为0
int* buffer = new int[512](); // 分配的512个int都初始化为0
int* buffer = new int(5); // 分配的一个int初始化为5

但是无法将分配的所有元素同时初始化为非0值,以下代码是不合法的:

int* buffer = new int[512](0); // 语法错误!!!
int* buffer = new int[512](5); // 语法错误!!!

C++11
C++11中增加了初始化列表功能,所以也可以使用以下的方式进行初始化:

int* buffer = new int{}; // 初始化为0
int* buffer = new int{0}; // 初始化为0
int* buffer = new int[512]{}; // 512个int都初始化为0
int* buffer = new int{5}; // 初始化为5

与上面不同的是,如下写法是合法的:

int* buffer = new int[512]{5}; // 第一个int初始化为5,其余初始化为0

但是其结果与我们设想的不一样,它并不是将分配的512个int都初始化为5,而仅仅是将第一个int初始化为5,其余的511个仍然初始化为0

而且正如初始化列表中“列表”两字所指出的,我们实际上可以用一个列表来初始化分配的内存:

int* buffer = new int[512]{1, 2, 3, 4}; // 前4个int分别初始化为1、2、3、4,其余int初始化为0
 

operator new 开辟内存 
constructor 内存初始化

 

4、返回类型安全性

new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

5、内存分配失败时的返回值

new内存分配失败时,会抛出bad_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL(要用if来判断 是否开辟成功)

try
{
    int *a = new int();
}
catch (bad_alloc)//内存动态分配错误
{
    ...
}

6、在使用上的区别

 new 返回指定类型的指针,并且可以自动计算所需要大小。而 malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。并且C++提供了new[]与delete[]来专门处理数组类型

7、new/delete会调用对象的构造函数/析构函数以完成对象的构造/析构,而malloc则不会

8、是否可以被重载

opeartor new /operator delete可以被重载。标准库是定义了operator new函数和operator delete函数的8个重载版本。而malloc/free并不允许重载。

9、new可以调用malloc ,但是malloc不能调用new
operator new可以重载 在重载的时候可以调用malloc  malloc是系统版本 兼容版本  向前兼容

10、能够直观地重新分配内存


使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。

new没有这样直观的配套设施来扩充内存。

总结

图来自: 动态内存分配、malloc与new的区别

有了malloc/free为什么还要new/delete?

  1. new运算不需要进行强制类型转换,使用简单方便;
  2. new运算是通过调用构造函数初始化动态创建的对象,执行效率更高;
  3. 使用new能够进行异常处理,使用更安全

 

二、ptmalloc

ptmalloc 实现了 malloc(),free()以及一组其它的函数. 以提供动态内存管理的支持。

malloc实现原理:

因为brk、sbrk、mmap都属于系统调用,若每次申请内存,都调用这三个,那么每次都会产生系统调用,影响性能;其次,这样申请的内存容易产生碎片,因为堆是从低地址到高地址,如果高地址的内存没有被释放,低地址的内存就不能被回收。 
   
  所以malloc采用的是内存池的管理方式(ptmalloc),Ptmalloc 采用边界标记法将内存划分成很多块,从而对内存的分配与回收进行管理。为了内存分配函数malloc的高效性,ptmalloc会预先向操作系统申请一块内存供用户使用,当我们申请和释放内存的时候,ptmalloc会将这些内存管理起来,并通过一些策略来判断是否将其回收给操作系统。这样做的最大好处就是,使用户申请和释放内存的时候更加高效,避免产生过多的内存碎片。
 

在没有用malloc 分配内存之前,堆区没有内存。

Linux 进程的默认地址空间, 对 heap 的操作, 操作系统提供了brk()系统调用,设置了Heap的上边界, 对 mmap 映射区域的操作,操作系 统 供了 mmap()和 munmap()函数。

因为系统调用的代价很高,不可能每次申请内存都从内核分配空间,尤其是对于小内存分配。 而且因为mmap的区域容易被munmap释放,所以一般大内存采用mmap(),小内存使用brk()。

在开辟<=128K时,brk指针会向后走,第一次申请的字节若不超过128k,系统会为其分配128k+4K的空间,如果多次malloc申请空间,每申请一次就需要修改一次mm_struct中brk的位置,就要执行一次系统调用,这样系统消耗会特别大,而多出来的4K的空间存放了分配信息,当free()时会用到。开辟的内存在用户空间的库上,当free时,这块空间归还给库,当进程结束时,归还系统。

在开辟〉128K时,通过mmap映射在堆和栈的中间映射一段内存,free时直接归还给系统。

不管内存是在哪里被分配的,用什么方法分配,用户请求分配的空间在ptmalloc中都使用一个chunk来表示。  用户调用free()函数释放掉的内存也并不是立即就归还给操作系统,相反,它们也会被表示为1个chunk, ptmalloc使用特定的数据结构来管理这些空闲的chunk。

malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用malloc动态申请内存块时,一定要进行返回值的判断。
 

参考: 进程地址空间

malloc的底层实现(ptmalloc)

 

 

 

 

 

 

 

 

 

 

<think>好的,我现在要详细解释一下mallocnew的底层原理实现。首先,我需要确认自己对这两个概念的理解是否正确。malloc是C语言中用于动态分配内存的函数,而newC++中的操作符,用于动态分配内存并调用构造函数。它们的底层实现可能有所不同,但都与操作系统的内存管理有关。 接下来,我需要回忆操作系统如何管理内存。通常,程序在运行时,内存分为堆、栈、全局变量区等。mallocnew主要涉及堆内存的分配。堆的内存管理由运行时库(如glibc)负责,它们向操作系统申请大块内存(例如通过sbrk或mmap系统调用),然后进行更细粒度的分配管理。 对于malloc的实现,常见的算法有隐式空闲链表、显式空闲链表、分离空闲链表等。glibc中的malloc可能使用了ptmalloc(pthreads malloc)实现,它支持多线程环境,通过维护多个内存块(称为arena)来减少锁的竞争。每个arena包含多个chunk,这些chunk根据大小被组织到不同的bins中,如fast bins、small bins、large binsunsorted bin。当分配内存时,malloc会先尝试在合适的bin中找到合适大小的空闲chunk,如果找不到,则可能通过brk或mmap向系统申请更多内存。 而newC++中不仅是分配内存,还会调用构造函数。它的实现通常依赖于malloc,即new操作符内部会调用malloc来获取内存,然后执行构造函数。此外,C++new可能会处理异常,如果分配失败则抛出std::bad_alloc异常,除非使用了nothrow版本。 需要注意,内存分配器需要考虑内存对齐、碎片问题(内部碎片外部碎片)、多线程性能等问题。例如,ptmalloc通过不同的arena来减少锁争用,而TCmalloc(Google的分配器)则使用线程本地缓存来提升性能。 可能还需要提到内存释放的过程,freedelete的区别。free只是释放内存,而delete会先调用析构函数再释放内存。此外,内存泄漏悬挂指针的问题也是需要注意的。 现在需要组织这些内容,确保结构清晰,分点讲解。可能需要先分别介绍mallocnew,再比较它们的区别,最后讨论底层实现细节,比如内存分配算法、系统调用等。 需要检查是否有错误的地方,比如是否混淆了mallocnew的实现细节,或者是否遗漏了关键点,比如对齐、线程安全、内存池等概念。同时,要确保使用正确的中文术语,避免直译英文术语导致理解困难。 最后,要确保符合用户的要求,详细且深入,但不过于冗长。可能需要分步骤讲解,从基本概念到实现细节,逐步深入。</think>### mallocnew的底层原理与实现详解 --- #### 一、基本概念与核心差异 1. **malloc** - C语言标准库函数,用于动态分配指定字节数的**未初始化内存**。 - 仅负责内存分配,不涉及对象构造。 - 返回`void*`指针,需手动类型转换。 2. **new** - C++操作符,用于动态分配内存**并调用构造函数初始化对象**。 - 类型安全,返回已类型化的指针。 - 失败时默认抛出`std::bad_alloc`异常(可通过`nothrow`禁用)。 --- #### 二、malloc的底层实现 ##### 1. **内存管理框架** - **系统调用依赖**:通过`sbrk`(扩展堆)或`mmap`(直接映射内存页)向操作系统申请大块内存。 - **内存池化**:运行时库(如glibc的`ptmalloc`)将申请的内存划分为**chunk**(内存块)管理。 - **Chunk结构**:包含元数据(如大小、状态)用户可用空间。 - **Bins分类**:按空闲chunk大小组织成不同链表(Fast bins、Small bins、Large bins等)。 ##### 2. **分配流程** - **快速分配**:优先从Fast bins(小内存,LIFO)或Small bins(固定大小链表)获取chunk。 - **合并与拆分**: - 若请求内存较大,遍历Large bins或Unsorted bin,拆分大chunk。 - 若所有bin均无可用chunk,通过`brk`/`mmap`申请新内存,生成新chunk。 - **内存对齐**:保证返回地址满足对齐要求(如8/16字节对齐)。 ##### 3. **释放流程(free)** - **标记为空闲**:将chunk标记为未使用,合并相邻空闲chunk以减少碎片。 - **缓存策略**:Fast bins中的chunk暂不合并,提升小内存分配效率。 --- #### 三、new的底层实现 ##### 1. **基本流程** - **内存分配**:底层调用`malloc`获取原始内存。 - **构造函数调用**:在分配的内存上执行对象构造函数。 - **异常处理**:若`malloc`返回`nullptr`,抛出`std::bad_alloc`。 ##### 2. **运算符重载** - 可重载`operator new`自定义内存分配逻辑(如内存池优化)。 - 示例代码: ```cpp void* operator new(size_t size) { if (void* p = custom_malloc(size)) return p; throw std::bad_alloc(); } ``` ##### 3. **delete的流程** - **析构函数调用**:先执行对象析构函数。 - **内存释放**:底层调用`free`归还内存。 --- #### 四、关键问题与优化策略 1. **内存碎片** - **内部碎片**:chunk大小对齐导致的浪费(如申请7字节,分配8字节)。 - **外部碎片**:分散的空闲chunk无法满足连续大内存请求。 - **解决策略**:Slab分配、伙伴系统(Buddy System)等。 2. **多线程优化** - **ptmalloc的Arena机制**:每个线程绑定独立的Arena,减少锁竞争。 - **TCMalloc(Google)**:线程本地缓存 + 全局Central Heap,提升并发性能。 3. **性能对比** - **malloc vs new**:`new`额外调用构造/析构函数,但现代编译器优化后差异微小。 - **分配器选择**:高频小对象场景可使用`jemalloc`或`TCMalloc`替代默认分配器。 --- #### 五、代码示例与调试技巧 1. **查看内存布局(Linux)** ```c void* ptr = malloc(1024); printf("Allocated address: %p\n", ptr); // 使用gdb的`heap`命令分析chunk信息 ``` 2. **检测内存泄漏(Valgrind)** ```bash valgrind --leak-check=full ./your_program ``` --- #### 六、总结对比表 | 特性 | malloc | new | |--------------------|----------------------------|------------------------------| | 语言 | C | C++ | | 初始化 | 不初始化 | 调用构造函数 | | 失败处理 | 返回NULL | 抛出异常(默认) | | 内存大小 | 手动计算(如`sizeof(int)`)| 自动推导类型大小 | | 重载支持 | 不支持 | 支持运算符重载 | | 底层依赖 | 直接系统调用或内存池 | 通常调用malloc | --- 通过理解这些机制,开发者能更高效地管理动态内存,避免泄漏碎片问题,并针对场景优化分配策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值