在Linux环境下
从操作系统角度看,进程分配内存有两种方式,分别由两个系统调用完成:brk 和 mmap (不考虑共享内存)
这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
malloc基本的实现原理就是维护一个内存空闲链表,当申请内存空间时,搜索内存空闲链表,找到适配的空闲内存空间,然后将空间分割成两个内存块,一个变成分配块,一个变成新的空闲块。如果没有搜索到,那么就会用sbrk()才推进brk指针来申请内存空间。
进程调用A=malloc(30K)以后,malloc函数会调用brk系统调用,将_edata指针往高地址推30K,就完成虚拟内存分配,难道这样就完成内存分配了?
默认情况下,malloc函数分配内存,如果请求内存大于128K(可由M_MMAP_THRESHOLD选项调节),那就不是去推_edata指针了,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存
这样子做主要是因为:
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数。
new在底层调用operator new函数申请空间,delete在底层通过operator delete函数释放空间。
operator new实际通过malloc申请空间,申请成功直接返回,申请失败就尝试空间不足的对应措施set_new_hander
,如果用户设置了这个措施就继续申请,否则抛出异常
当申请内存不够时就会抛出out of memory
的异常。但是有时,我们希望能够调用自己定制的异常处理函数,优先处理。这就是set_new_handler
。
// 定义在中
namespace std{
typedef void (*new_handler)();
std::new_handler set_new_handler( std::new_handler new_p );
}
#include
void out_of_memory() {
std::cout<<"out of memory!"<<std::endl;
}
int main() {
std::set_new_handler(out_of_memory);
try{
int *p = new int[0x1fffffff];
}catch(std::exception& e){
std::cout << e.what() << std::endl;
}
return 0;
}
new操作符(new operator)与operator new的关系(new操作符调用operator new)
new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图方法自己没被授权的内存区域。
new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。在使用C语言时,我们习惯在malloc分配内存后判断分配是否成功:
int *a = (int *)malloc(sizeof(int));
if(NULL == a) {...}
else {...}
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。
class ClassA {...};
A *ptr = new A;
A *ptr = (A *)malloc(sizeof(A));
使用new操作符来分配对象内存时会经历三个步骤:
使用delete操作符来释放对象内存时会经历两个步骤:
总之来说,new/delete会调用对象的构造函数/析构函数以完成对象的构造/析构。而malloc则不会
C++提供了new[]与delete[]来专门处理数组类型:
A * ptr = new A[10];//分配10个A对象
使用new[]分配的内存必须使用delete[]进行释放:
delete [] ptr;
new对数组的支持体现在它会分别调用构造函数函数初始化每一个数组元素,释放对象时为每个对象调用析构函数。注意delete[]要与new[]配套使用,不然会找出数组对象部分释放的现象,造成内存泄漏。
至于malloc,它并知道你在这块内存上要放的数组还是啥别的东西,反正它就给你一块原始的内存,在给你个内存的地址就完事。所以如果要动态分配一个数组的内存,还需要我们手动自定数组的大小:
int * ptr = (int *) malloc( sizeof(int) );//分配一个10个int元素的数组
operator new /operator delete的实现可以基于malloc,而malloc的实现不可以去调用new。
opeartor new /operator delete可以被重载。标准库是定义了operator new函数和operator delete函数的8个重载版本,而malloc/free并不允许重载。
//这些版本可能抛出异常
void * operator new(size_t);
void * operator new[](size_t);
void * operator delete (void * )noexcept;
void * operator delete[](void *0)noexcept;
//这些版本承诺不抛出异常
void * operator new(size_t ,nothrow_t&) noexcept;
void * operator new[](size_t, nothrow_t& );
void * operator delete (void *,nothrow_t& )noexcept;
void * operator delete[](void *0,nothrow_t& )noexcept;
使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。
new没有这样直观的配套设施来扩充内存。
在operator new抛出异常以反映一个未获得满足的需求之前,它会先调用一个用户指定的错误处理函数new_handler
,可以通过set_new_handler
设置
C++ new/delete详解及原理 - Duikerdd - 博客园 (cnblogs.com)
malloc 底层实现及原理 - 爱笑的张飞 - 博客园 (cnblogs.com)
C++ 中 new 操作符内幕:new operator、operator new、placement new - slgkaifa - 博客园 (cnblogs.com)
c++ new 与malloc有什么区别 - ywliao - 博客园 (cnblogs.com)