动态分配和撤销内存的运算符new/delete和函数malloc()/free()

2015/05/20 C和C++基础

在软件开发过程中,常常需要动态地分配和撤销内存空间,例如对动态链表中结点的插入与删除。在 C语言中是利用库函数malloc和free来分配和撤销内存空间的。C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数。注意:new和delete是运算符,不是函数,因此执行效率高。虽然为了与C语言兼容,C++仍保留malloc和free函数,但建议用户不用malloc和free函数,而用new 和delete运算符。

new运算符

//开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
int *a=new int;

//开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址
int *b=new int(100);

//开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
char *c=new char[10];
cout<<sizeof(c)<<endl;          //输出值为4

//开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址
int (*d)[4]=new int[5][4];      //d为数组指针

//开辟一个存放单精度数的空间,并指定该实数的初值为3.14159,将返回的该空间的地址赋给指针变量p
float *p=new float(3.14159);

用new分配数组空间时不能指定初值。如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分配空间是否成功。

delete运算符

要撤销上面用new开辟的存放单精度数的空间(上面的例子),应该用:

delete p;

前面用“new char[10];”开辟的字符数组空间,如果把new返回的指针赋给了指针变量c,则应该用以下形式的delete运算符撤销该空间:

delete []c;  //在指针变量前面加一对方括号,表示是对数组空间的操作

在delete之后,重设指针的值

delete p; 在执行完该语句后,p变成了不确定的指针,在很多机器上,尽管p值没有明确定义,但仍然存放了它之前所指对象的地址,然后p所指向的内存已经被释放了,所以p不再有效。此时,该指针变成了悬垂指针(悬垂指针指向曾经存放对象的内存,但该对象已经不存在了)。悬垂指针往往导致程序错误,而且很难检测出来。因此一旦删除了指针所指的对象,应立即将指针置为0,这样就非常清楚的指明指针不再指向任何对象。(零值指针:int *ip=0;)

区分零值指针和NULL指针

零值指针,是值是0的指针,可以是任何一种指针类型,可以是通用变体类型void也可以是char,int*等等。空指针,其实空指针只是一种编程概念,就如一个容器可能有空和非空两种基本状态,而在非空时可能里面存储了一个数值是0,因此空指针是人为认为的指针不提供任何地址讯息。

malloc()函数

函数原型: void *malloc(int size);

说明:malloc 向系统申请分配指定size个字节的内存空间,返回类型是 void* 类型。void* 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。    备注:void* 表示未确定类型的指针,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据(比如是char还是int或者…)。

第一、malloc 函数返回的是 void * 类型。对于C++,如果你写成:p = malloc (sizeof(int)); 则程序无法通过编译,报错:“不能将 void* 赋值给 int * 类型变量”。所以必须通过 (int *) 来将强制转换。而对于C,没有这个要求,但为了使C程序更方便的移植到C++中来,建议养成强制转换的习惯。

第二、函数的实参为sizeof(int),用于指明一个整型数据需要的大小。如果你写成:

int* p = (int *) malloc (1);

代码也能通过编译,但事实上只分配了1个字节大小的内存空间,当你往里头存入一个整数,就会有3个字节无家可归,而直接“住进邻居家”!造成的结果是后面的内存中原有数据内容被改写。

malloc函数的实质

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

申请的时候实际上占用的内存要比申请的大。因为超出的空间是用来记录对这块内存的管理信息。大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度,指向下一个分配块的指针等等。这就意味着如果写过一个已分配区的尾端,则会改写后一块的管理信息,这种类型的错误是灾难性的。因此malloc()申请的空间实际上就是分成两个不同性质的空间。一个就是用来记录管理信息的空间,另外一个就是可用空间了。而用来记录管理信息的实际上是一个结构体。这个结构体的原型:

struct mem_control_block { 
	int is_available;    //标记该空间是否可用 
	int size;            //这是实际空间的大小 
};

malloc()到底从哪里得来了内存空间

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

free()函数

函数原型:void free(void *FirstByte)

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

free()到底释放了什么

free()释放的是指针指向的内存!注意!释放的是内存,不是指针!这点非常非常重要!指针是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在!只不过现在指针指向的内容的垃圾,是未定义的,所以说是垃圾。因此,释放内存后把指针指向NULL,防止指针在后面不小心又被解引用了。非常重要啊这一点!

free()的机制

free()的源代码:

void free(void *ptr)  
{ 
	struct mem_control_block *free; 
    free = ptr - sizeof(struct mem_control_block); 
    free->is_available = 1; 
    return; 
}

看一下函数第二句,这句非常重要和关键。其实这句就是把指向可用空间的指针倒回去,让它指向管理信息的那块空间,因为这里是在值上减去了一个结构体的大小。那么就free()这个源代码来看,什么也没有释放,其实,free()只是记录了一些信息,然后告诉操作系统哪一块内存可以去释放,释放是操作系统的事,后面那一句free->is_available = 1;就告诉操作系统这部分空间可以释放了。

之前有个错误的认识,就是认为指向那块内存的指针不管移到那块内存中的哪个位置都可以释放那块内存!但是,这是大错特错!释放是不可以释放一部分的!首先这点应该要明白。而且,从free()的源代码看,ptr只能指向可用空间的首地址,不然,减去结构体大小之后一定不是指向管理信息空间的首地址。所以,要确保指针指向可用空间的首地址!

malloc/free的主要事项

1)申请了内存空间后,必须检查是否分配成功。

2)当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。

malloc和new的区别

1.malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

2.malloc只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。 除了分配及最后释放的方法不一样以外,通过malloc或new得到指针,在其它操作上保持一致。

3.new 返回指定类型的指针,并且可以自动计算所需要大小。

//返回类型为int* 类型(整数型指针),分配大小为 sizeof(int);
int *p;
p = new int;  

//返回类型为 int* 类型(整数型指针),分配大小为 sizeof(int) * 100;
int* parr;
parr = new int [100]; 

而 malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。

//分配128个(可根据实际需要替换该数值)整型存储单元,
//并将这128个连续的整型存储单元的首地址存储到指针变量p中
int* p;
p = (int *) malloc (sizeof(int)*128);

//分配12个double型存储单元,
//并将首地址存储到指针变量pd中
double *pd=(double *) malloc (sizeof(double)*12);

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

对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。

既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?

这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

注意:如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果delete释放“malloc申请的动态内存”,结果也会导致程序出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。

Search

    Post Directory