malloc/calloc/realloc/free与new/delete的对比

来源:互联网 发布:php 逗号分隔字符串 编辑:程序博客网 时间:2024/05/21 09:57


一、C语言中动态申请内存(函数)

【堆上】

(1)malloc函数:

函数原型:  void* malloc(size_t size);   void* malloc(count *sizeof(*pointer));

函数功能:动态申请size个字节大小的内存空间,返回该段空间的首地址,该段空间里面的内容是随机值。

函数参数:malloc()函数有一个参数,即要分配的内存空间的大小。

返回值:始终是void* ;申请成功,返回空间首地址,否则,返回NULL;使用这个函数一定要对返回值进行判断。malloc返回的内存是“没有”初始

一个错误的例子:

int *p=malloc(10*4);

首先没有对返回值进行强制类型转换,编译时会出现警告。其次,参数给了4,只是32位机器下的一个整型的长度,可移植性差。最后,没有进行参数检测,万一分配内存 失败,后面对这个指针的操作可能会导致崩溃!

建议像下面这样使用malloc:


  int *p = (int*)malloc(n*sizeof(int));   if (NULL == p)    {   //do something or exit    }

(2)calloc函数:

函数原型:void*calloc(size_t nmemb,size_t size);

函数功能:动态申请num个元素数组的内存块,申请的空间会用0来初始化。

函数参数:calooc函数有两个参数,分别为元素的数目num和每个元素的大小size,这两个参数的乘机就是要分配的内存空间的大小。

返回值:申请成功,返回空间的首地址,否则返回NULL。使用时一定要注意检测是否开辟成功。


(3)、realloc函数:

函数原型:void* realloc(void* mem_address,unsigned int newsize);

参数说明:ptr:需要改变的指针; size:要改变的字节数byte,可比原内存空间大或者小。

函数功能:先判断当前指针指向的内存块后面有没有足够大的连续内存空间,如果有扩大,直接返回原地址。如果指向的内存块之后没有足够大的内存空间,则重新开辟size大小的空间,将原有的数据从头到尾拷贝到新的内存空间中去,然后将原空间释放,最后返回新分配空间的首地址。

注意:

1、realloc失败的时候返回NULL;

2、realloc失败的时候,原来的内存空间不会改变,不会释放也不会移动。

3、如果size为0,效果等同于free,只对指针所指内存进行释放,对于二级指针** realloc时,只会释放一维,注意使用时谨防内存泄漏。

4、传递给realloc的指针,必须是先经过malooc/calooc/realooc申请的。

5、当ptr为NULL时,等同于malloc。

void TestMemory(){// Malloc  int * pTest = (int*)malloc(10 * sizeof(int));DoSomething()if (pTest != NULL){free(pTest);pTest = NULL;}// calloc 该函数会将申请的内存空间初始化为0  int * pTest1 = (int*)calloc(10, sizeof(int));    DoSomething();if (pTest != NULL){free(pTest);pTest = NULL;}// rellock,改变原有内存空间大小,若不能改变,则将会开辟一段新的内存,  //将原有内存的内容拷贝过去,  // 但不会对新开辟的空间进行初始化  int * pTest2 = (int*)malloc(10 * sizeof(int));realloc(pTest2, 100 * sizeof(int));free(pTest2);}

以上3个空间使用完后,一定要记得释放掉那块空间,否则会引起内存泄漏,这在大的项目中是一件非常重要的事情。

(4)、free函数:

指针被free以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p就成了“野指针”。如果此时不把p设置为NULL,会让人误以为p是个合法的指针。我们有时记不住p所指的内存是否已经被释放了,在释放p之前,先用语句if(p!=NULL)对p进行防错处理。

【常见的内存泄漏举例】

void MemoryLeaks(){// 1、内存申请了忘记释放  int *pTest = (int *)malloc(10 * sizeof(int));assert(NULL != pTest);DoSomething();// 2、程序逻辑不清,以为释放了,实际内存泄露  int *pTest1 = (int *)malloc(10 * sizeof(int));int *pTest2 = (int *)malloc(10 * sizeof(int)); DoSomething();pTest1 = pTest2;free(pTest1);free(pTest2);// 3、程序误操作,将堆破坏  char *pTest3 = (char *)malloc(5);strcpy(pTest3, "Memory Leaks!");free(pTest3);// 4、释放时传入的地址和申请时的地方不相同  int *pTest4 = (int *)malloc(10 * sizeof(int));assert(NULL != pTest4);pTest4[0] = 0;pTest4++;DoSomething();free(pTest4);}

【栈上】

使用_alloc(VS编译器)或者alloc(gcc编译器)在栈上开辟内存,栈上开辟的内存由便一起自动维护,不需要显示的释放。用法同malloc。


以上的几个函数是C语言中的函数,在C++中也可以使用。

二、C++中动态内存管理(运算符)

(1)、【new/delete运算符】

1、new分配空间看起来比C中动态内存开辟空间简单,而且无需自己计算所需内存的大小,返回值也无需强制类型转换。

2、new不只是分配内存,而且会调用类的构造函数,同理delete会调用类的析构函数。

3、newdelete要匹配使用;deletefree在释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。

void Test{int *p4 = new int();// 动态分配4个字节(1个 int)的空间 int *p5 = new int(3);// 动态分配4个字节(1个 int)的空间并初始化为3 int *p6 = new int[3];// 动态分配12个字节(3个 int)的空间  delete p4;delete p5;delete[] p6;}

new和delete、new[]和delete[]一定要匹配使用,否则可能出现内存泄漏甚至崩溃的问题。

(2)、new/delete和new[]/delete[]:

1、new/delete:

其实我们用new来申请一段空间时,编译器会先调用operator new函数,然后再调用构造函数(如果有的话)进行初始化。其中,在operator new函数中,又调用了malloc函数,即operator new是malloc的封装;用delete来释放空间,编译器会先调用一次析构函数,然后才调用free释放那段空间。

2、new[]/delete[]:

再来看看new [] 和 delete [],以test *p2 = new test[10];来举例说明。

如上,编译器总共开辟了44个字节,其中后40个字节用来存放对象,new[]返回的1指针就指向这40个字节的首地址。前4个字节存放了对象个数,它的作用先不管,看一看new[]是怎么做的:

其实new []是对operator new[] 的一个封装,用new[]来分配空间时,调用了operator new[],然后在operator new[]内,将所申请的内存空间大小计算出来,例如上边例子中就是10*sizeof(test),然后在这个基础上加4,变成了44(字节),而operator new[]又封装了operator new,接着又调用了operator new。所以现在清楚了吧!真正申请的空间大小多了4字节。调用完operator new[]后,又将返回的指针向后偏移4个字节,并依次调用10次构造函数,完成对对象的初始化,然后才将这个偏移4字节后的指针返回去。也就是用户看到的指针了。

再看delete[],它其实封装了operator delete[] 和free,用delete[]释放空间时,先根据传进来指针所指向空间的前4个字节的内容(即对象的个数,假设为n),调用n次析构函数(反着来的,先构造的后析构),然后将这个地址传递给operator delete[],operator delete[]封装了operator delete,它先计算出这段空间的真正首地址,即将传进来的指针向前偏移4个字节,然后调用operator delete。

(3)、总结一下:

【new作用】
调用operator new分配空间。
调用构造函数初始化对象。
【delete作用】
调用析构函数清理对象
调用operator delete释放空间
【new[]作用】
调用operator new分配空间。
调用N次构造函数分别初始化每个对象。
【delete[]作用】
调用N次析构函数清理对象。
调用operator delete释放空间。

(4)、定位new表达式:

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

注意:C++中的构造函数是不能显示调用的,这里只是变相的调用了构造函数。

test *p = new test[10];new(p)test;new(p + 1) test;//...  new(p + 9) test;

像上面这样,就完成了对所申请空间的初始化,借用定位new表达式,malloc和free,再加上显示的调用析构函数,可以模拟出new/delete和new[]/delete[]的行为。

三、最后的总结:【malloc/free和new/delete的区别和联系

  1. 它们都是动态管理内存的入口
  2. malloc/free是C/C++标准库的函数,new/delete是C++操作符
  3. malloc/free只是动态分配内存空间/释放空间。而new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理(清理成员)
  4. malloc/free需要手动计算类型大小且返回值会void*,new/delete可自己计算类型的大小,返回对应类型的指针
  5. 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free
  6. 它们都需要各自配对使用

四:补充内存分配的方式:

内存分配方式:

(1)、静态存储区域分配

内存在程序编译的时候就已经分配好,这块内存在程序的整个运行周期都存在。例如全局变量,static变量。

(2)、栈上创建

在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动给被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限

(3)、堆上分配,亦称动态内存分配

程序在运行的时候用mallocnew申请任意的内存,程序员自己负责在任何时候用freedelete释放内存。动态内存的生存期由我们决定,使用灵活,但问题也最多。


(4)、注意

如果函数的参数是一个指针,一般不用指针去开辟空间,非要用指针去开辟空间的时候,就要用指向指针的指针。c/c++语言没有办法知道指针所指的内存容量,除非在申请的时候记住它。内存申请失败的处理方式:return;语句或者exit(1);语句,再或者是c++中的异常处理方式。








0 0
原创粉丝点击