C语言编程基础-malloc和new

序言

C语言中没有new操作符,分配空间常用的是malloc()函数。有时候技术面会问到这两个问题,在这里总结一下。


1. malloc函数 (C/C++)

malloc的全称是memory allocation,动态内存分配。

  • 原型:extern void *malloc(unsigned int num_bytes);

  • 说明:分配长度为num_bytes字节的内存块。

  • 返回值:如果分配成功则返回指向被分配内存的指针,分配失败返回空指针NULL。

  • 其他:当内存不再使用时,应使用free()函数将内存块释放。

void *malloc(int size);

  • 说明:
    malloc 向系统申请分配指定size个字节的内存空间,返回类型是 void* 类型。
    void* 表示未确定类型的指针。C/C++规定,void* 类型可以强制转换为任何其它类型的指针

  • void* 表示未确定类型的指针,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据。

void *free(int *Pointer);

  • 说明:
    该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。

  • 注意:
    参数是指向内存块的指针,释放的是内存不是指针本身。内存释放后,指针指向未定义的内容垃圾,所以经常做如下处理,防止产生野指针,又被解引用。

char *p = (char *)malloc(sizeof(char) * 8);
memset(p, 0 ,8);
    .
    .
    .
free(p);
p = NULL;

malloc()如何分配内存
malloc从堆里面获得空间,也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

void Fun(void)
{
    char *p = (char *)malloc(100 * sizeof(char)); 
}

如果在函数里定义了一个指针变量,然后在这个函数里申请了一块内存让指针指向它。
实际上,这个指针的地址是在栈上,但是它所指向的内容却是在堆上面的。

注意:函数所在的被销毁指针也跟着销毁,但申请的内存并没有一样跟着销毁!因为申请的内存在堆上,而函数所在的栈被销毁跟堆完全没有关系。所以一定记得释放free!


2. new操作符 (C++)

new 与delete是C++预定的操作符,一般需配套使用。
new用于从堆内存申请一块空间,一般用于动态申请内存空间,即根据程序需要,申请一定长度的空间,而delete则是将new申请的空间释放。

new的三种申请内存空间格式
new  数据类型

new 数据类型(初始值)

new 数据类型[常量表达式]

int  *p1 = new int;

int  *p2 = new int(100);    //*p2初始化为100

int  *p3 = new int[1000];  //申请1000个int类型的空间

说明:

  • int *p1 = new int;
    动态创建对象,只指定其数据类型,而不为该对象命名。
    new表达式返回指向该新创建对象的指针,我们可以通过指针来访问此对象。
    该new表达式在堆区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针p1,p1指向一个没有初始化的int。

  • int *p2 = new int(100);
    动态创建的对象可以用初始化变量的方式初始化,指针p2所指向的对象初始化为100。

  • int *p3 = new int[1000];
    申请1000个int类型的空间。

由于系统资源有限,用new申请内存空间,并一定任何时候都能申请到足够的内存空间。
一般申请完后应加一个条件进行判断

if (p3 != NULL)
{
    ... //程序代码
}
else
{
    ... //抛出异常,内存空间申请失败
}
new对应的三种释放内存方式

内存用new申请后,一定要释放,主要用delete

void FreeMem()
{
    int  i;
    int *p0=&i;
    int  * p1=new int;
    int  *p2=new int(100);  //*p2初始化为100
    int  *p3=new int[1000]  //申请1000个int类型空间

  // 使用以上指针后,最后要予以释放内存空间

    delete p0;              //错误!p0指针不是用new动态申请的

  //下面三个是正确的写法
    delete p1;
    delete p2;
    delete []p3;            //不能用delete p3,申请用了[],释放时要用delete[]
}
分配/释放对象内存的过程
  • 使用new操作符来分配对象内存时会经历三个步骤:

    • 第一步:调用operator new 函数(对于数组是operator new[])分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。
    • 第二步:编译器运行相应的构造函数以构造对象,并为其传入初值。
    • 第三部:对象构造完成后,返回一个指向该对象的指针。
  • 使用delete操作符来释放对象内存时会经历两个步骤:

    • 第一步:调用对象的析构函数
    • 第二步:编译器调用operator delete(或operator delete[])函数释放内存空间。


3. c++中new的三种用法

plain new/delete 普通的new,就是我们惯常使用的new
nothrow new/delete 不抛出异常的运算符new的形式
placement new/delete 允许在一块已经分配成功的内存上重新构造对象或对象数组

(1) plain new
  • 定义:
    void* operator new(std::size_t) throw(std::bad_alloc);
    void operator delete(void *) throw();
  • 返回值:标准C++ plain new失败后抛出标准异常std::bad_alloc非返回NULL,因此检查返回值是否为NULL判断分配是否成功是徒劳的
  • 示例代码
#include 
#include "stdafx.h"
using namespace std;

char *GetMemory(unsigned long size)
{
    char *p = new char[size];   //分配失败,非返回NULL
    return p;
}

int main()
{
    try                         //使用异常机制
    {
      char *p = GetMemory(10e11); //分配失败抛出异常std::bad_alloc
      .
      .
      .
      if(!p)            //无效
      cout<<"failure"<delete [] p;
    }

    catch(const std::bad_alloc &ex)  //catch异常
    {
        cout<return 0;
}
(2) nothrow new
  • 定义
    void* operator new(std::size_t,const std::nothrow_t&) throw();
    void operator delete(void*) throw();
  • 返回值:nothrow new在失败时,返回NULL
  • 示例代码
#include 
#include "stdafx.h"
#include            //使用时要包含头文件
using namespace std;

char *GetMemory(unsigned long size)
{
    char *p = new(nothrow) char[size];  //分配失败,是返回NULL
    if(NULL == p)
      cout<<"alloc failure!"<return p;
}

int main()
{
    try                           //使用异常机制
    {
      char *p = GetMemory(10e11);
      .
      .
      .
      if(p == NULL)  //正常
        cout<<"failure"<delete [] p;
    }

    catch(const std::bad_alloc &ex)
    {
      cout<return 0;
}
(3) placement new

placement new不用担心内存分配失败,因为它根本不分配内存,它做的唯一一件事情就是调用对象的构造函数

  • 定义
    void* operator new(size_t,void*);
    void operator delete(void*,void*);
  • palcement new的主要用途就是反复使用一块较大的动态分配的内存来构造不同类型的对象或者他们的数组。
    例如先申请一个足够大的字符数组,然后当需要时在它上面构造不同类型的对象或数组。

  • placement new构造起来的对象或数组,要显示调用他们的析构函数来销毁,千万不要使用delete。
    因为placement new构造起来的对象或数组大小并不一定等于原来分配的内存大小,使用delete会造成内存泄漏或者之后释放内存时出现运行错误

  • 返回值:placement new不用担心内存分配失败,因为它根本分配内存,只是调用对象的构造函数

  • 示例代码
#include 
#include "stdafx.h"
#include             //使用时要包含头文件
using namespace std;

class ADT
{
    int i;
    int j;
    public:
        ADT()
        {
        }
        ~ADT()
        {
        }
};

int main()
{
    //nothrow new
    char *p=new(nothrow) char[sizeof(ADT)+2];
    if(p == NULL)
      cout<<"failure"<delete []p;

    //placement new
    ADT *q=new(p) ADT;  //placement new,不用担心内存分配失败
    q->ADT::~ADT();     //显示调用析构函数来销毁

    // delete q;        //错误! 不能在此处调用delete q;

    return 0;
}

4. malloc()和new的区别

区别 malloc/free new/delete
分配内存的位置 自由存储区
内存分配成功的返回值 void* 完整类型指针
内存分配失败的返回值 返回NULL 默认抛出异常
分配内存的大小 必须显式指定字节数 由编译器根据类型计算得出
已分配内存的扩充 使用realloc简单完成 无法直观地处理
是否相互调用 不可调用new 可以,看具体的operator new/delete实现
函数重载 不允许 允许
构造/析构函数 不调用 调用
分配内存时内存不足 返回NULL,无法通过用户代码进行处理 调用用户自定义错误处理函数new_handler

(1) C++中可以使用new操作符也可以使用malloc函数,而C中只能使用malloc函数。

(2) new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。
自由存储区不仅可以是,还可以是静态存储区,取决于operator new在哪里为对象分配内存

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

(4) (普通)new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
malloc分配内存后判断分配是否成功

int *a  = (int *)malloc ( sizeof (int ));
if(NULL == a)
{
    ...
}
else 
{
    ...
}

new使用异常机制判断内存是否分配成功

try
{
    int *a = new int();
}
catch (bad_alloc)
{
    ...
}

(5) malloc需显式地指出所需内存大小;new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。

(6) new/delete会调用对象的构造/析构函数以完成对象的构造/析构;而malloc则不会。
故标准库中凡是需要构造/析构的类型通通不合适用malloc/free来处理

(7) malloc和new是否可以相互调用
malloc的实现不可以去调用new,而operator new /operator delete的实现可以基于malloc

(8) malloc和new是否可以被重载
malloc/free不允许重载;opeartor new /operator delete可以被重载,标准库定义了operator new函数和operator delete函数的8个重载版本

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



Acknowledgements:
http://blog.csdn.net/oNever_say_love/article/details/50724190
http://blog.csdn.net/psy0324/article/details/4794723
http://www.cnblogs.com/QG-whz/p/5140930.html
http://www.jb51.net/article/41524.htm

2017.04.23

你可能感兴趣的:(C/C++,c语言)