new分配内存的详细解释

来源:互联网 发布:哪个软件播放rmvb 编辑:程序博客网 时间:2024/06/05 00:37

用new 来在堆上分配内存时,使用的都是new operator,即平时使用最多的new。 new operator(C++中的new有三重含义, operator new,new operator, placement new)在申请内存的时候,首先会调用operator new 来分配内存,operator new在申请内存失败时,缺省行为是抛出异常,一旦抛出异常,而不对异常进行捕获处理,程序会退出或者崩溃。

operator new同大多数运输符一样,可以重载,全局的和局部的都已重载。void* operator new (std::size_t size, ... ) ,重载时,operator new可以带有多个参数,只要第一个参数是size_t 类型即可,实际在使用时,这个实参是隐式传入的 ,由分配内存的new operator 后边的类型决定,剩下的参数由new operator 带入。

class A{public: void* operator new(size_t size, T1 arg1, T2 arg2)};//调用形式如下,size参数是隐式传入的。new(par1, par2) A;  




一、缺省失败行为

int main () {   char* p = nullptr;    while(true)   {      p = new char [1024*1024*1024];      std::cout << "Ok\n";   }  return 0;}


二、异常捕获

可以对抛出的异常进行捕获,防止程序异常退出:

int main () {  char* p = nullptr;  try  {    while(true)    {      p = new char [1024*1024*1024];      std::cout << "Ok\n";     }  } catch ( exception e )   {      cout << e.what() << " xxx" << endl;   }  return 0;}


C++允许用户自定义申请失败的行为,此时,operator new申请内存失败的处理过程如下:

1、调用用户指定的失败处理处理函数;

2、重行申请内存分配请求;

3、如果成功,返回内存指针;如果失败,再次转向步骤1。

可以看到,这里是有一个循环的,为了能够跳出循环,用户可以在失败处理函数中进行整理释放已经申请过的内存,以使接下来的再次申请能够成功。

三、自定义失败处理行为

用户自定义的失败处理函数通过#include <new>这个头文件中的new_handler set_new_handler (new_handler new_p) throw() 函数来设置,这里的new_handler 是个类型定义,像size_t, ptrdiff_t一样,是个typedef: typedef void (*new_handler)(); 其实就是一个函数指针类型,形参new_p 刚好就指向失败处理函数的函数指针。

void no_memory () {    static int i = 0;    ++i;    std::cout << "Failed to allocate memory!\n";    if(i == 10)    {        std::exit (1);   //尝试10次还未成功就推出程序    }}int main () {    std::set_new_handler(no_memory);    char* p = nullptr;    try    {        while(true)        {            p = new char [1024*1024*1024];            std::cout << "Ok\n";        }    }  catch ( exception e )   {      cout << e.what() << " xxx" << endl;   }  return 0;}
运行结果如下:

可以看到,虽然进行了异常处理,但是在自定义的失败处理函数中,并没有抛出异常,所以会一直循环的去尝试申请内存。

四、自定义失败处理中抛出异常

如果在失败处理函数中抛出异常:

void no_memory (){    static int i = 0;    ++i;    std::cout << "Failed to allocate memory!\n";    if(i == 10)    {        throw bad_alloc();   //在这里抛出异常    }}int main () {    std::set_new_handler(no_memory);    char* p = nullptr;    try    {        while(true)        {            p = new char [1024*1024*1024];            std::cout << "Ok\n";        }    }  catch ( exception e )   {      cout << e.what() << " xxx" << endl;   }  return 0;}



五、动态的更改失败行为

如果当前的失败处理函数的处理在经过一定次数的尝试仍然不能使分配成功,可以考虑在运行时更改处理函数为另外一个处理函数,之后的失败处理函数的调用为最新的那个有效

void another_handler()    //另外一个失败处理函数{    cout << "Failed in this handler!\n";    throw bad_alloc();}void no_memory (){    static int i = 0;    ++i;    std::cout << "Failed to allocate memory!\n";    if(i == 10)    {        set_new_handler(another_handler);    //在失败处理函数中重新设置失败处理函数    }}int main () {    std::set_new_handler(no_memory);    char* p = nullptr;    try    {        while(true)        {            p = new char [1024*1024*1024];            std::cout << "Ok\n";        }    }  catch ( exception e )   {      cout << e.what() << " xxx" << endl;   }  return 0;}
运行结果如下:


可以看到失败处理函数已经更改。

当然如果想恢复缺省的失败处理行为,只要给 set_new_handler() 传入一个空指针就行。


下边内容来自这里点击打开链接

malloc是C库函数。用于分配堆内存。是在运行时分配存储空间的函数。
头文件: stdlib.h
函数原型:void *malloc(unsigned int num_bytes);

C库函数malloc

1.组织结构

空闲存储空间以空闲链表的方式组织,每个块包含一个长度,一个指向下一块的指针,以及一个指向自身存储空间的指针。按照存储地址升序组织。最后一块指向第一块。
malloc的内存块结构malloc的内存块结构

2.分配过程

有申请时,malloc顺序扫描空闲链表。直到找到一个足够大的块。算法为“首次适应”。寻找过程中,如果块大小等于需求大小,直接返回;如果块大小太大,分成两部分,一块为需求大小,将它返回(一般是块的后半部分),另一块留在链表中;如果找不到足够大的块。调用sbrk向操作系统申请更大块加入空闲链表。

3.释放过程

free首先搜索空闲块链表,找到相邻空闲块,则合并为一块。避免碎片。
malloc块链表malloc块链表

Unix系统调用sbrk

函数原型:char *sbrk(int incr);

它返回下一个内存空间。则sbrk(0)能得知堆的结束地址program break.
sbrk是一个Linux的系统调用。它增加或者减少堆空间的长度。完成虚拟地址到物理地址的映射。在malloc申请空间不足时,malloc会调用sbrk来申请更多的内存加入到空闲块链表上。如下图,sbrk就是移动堆的program break的位置。
Linux进程堆内存布局Linux进程堆内存布局

malloc/free与new/delete

  1. malloc是C标准库函数,new是C++的运算符。
  2. 对于用户自定义的对象而言,用malloc/free不能够执行构造函数和析构函数。因为malloc是库函数,不受编译器控制。
  3. 对于内置的数据对象,比如int char等,用new 和用malloc都可以。



1 0
原创粉丝点击