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

来源:互联网 发布:4y4淘宝店铺装修安全吗 编辑:程序博客网 时间:2024/05/17 23:41

C动态申请内存函数:

【堆上】

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

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

返回值:始终是void*,申请成功,返回空间的首地址,否则返回NULL,所以使用这个函数一定要对返回值进行判断。

例:int *p = malloc(10 * 4);

首先没有对返回值进行强制转换,编译时会报警告;其次,参数给了个4,只是代表32为机器下一个整型的长度,可移植性不好;最后,没有进行参数检测,万一分配失败,则下面对这个指针的操作1可能会导致崩溃。建议像下面这样使用:

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


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

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

num:元素的个数

size:元素类型大小

返回值:申请成功,返回该段内存的首地址,申请失败返回NULL,使用时一定要注意监测是否分配成功。


函数原型:void *realloc(void *ptr, size_t size);

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

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

注意:

  1. realloc失败的时候返回NULL;
  2. realloc失败的时候,原来的内存空间不改变,不会释放也不会移动;
  3. 如果size为0,效果等同于free,只对指针所指内存进行释放;对于二级指针**a realloc时,只会释放一维,注意使用时谨防内存泄漏;
  4. 传递给realloc的指针必须是先前通过malloc、calloc、realloc申请的;
  5. 当ptr为NULL时,该函数等同于malloc

void TestMemory (){// Mallocint * pTest = ( int*) malloc(10 * sizeof( int));DoSomething();if ( pTest != NULL){free( pTest);pTest = NULL;}// calloc 该函数会将申请的内存空间初始化为0int * 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个函数使用完后一定要free掉那段空间,否则会引起内存泄漏。这在大的项目中是一件很可怕的事情。

【常见的内存泄露】

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)或alloca(gcc)在栈上动态开辟内存,栈上开辟的内存由编译器自动维护,不需要用户显式释放。用法同malloc。


以上几个函数是C语言中的函数,在C++中也可以使用。下面再来看看C++自己的动态内存开辟方法:

【new/delete 运算符】

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

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

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

void Test (){// 以下代码没有匹配使用,会发生什么?有内存泄露吗?会崩溃吗?int* p4 = new int;int* p5 = new int(3);int* p6 = new int[3];int* p7 = (int*) malloc(sizeof (int));delete[] p4 ;delete p5 ;free(p5 );delete p6 ;delete p7 ;}
以上代码虽然没有配对使用,但也不会出错。但下边的例子就会出错了。

class test{public:test(){}~test(){}       int i;}int main(){test *p1 = new test;//delete[] p1;test *p2 = new test[10];//delete p2;//free p2;return 0;}
用以上注释中的方法释放空间时,程序都将会崩溃。当注释掉test中的析构函数后,即使不配对使用,程序又会正常运行。下面来解释下这种原因。

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

再来看看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。

看看这张图,你会更清晰:


现在明白了吧,上边的

test *p1 = new test;  //delete[] p1;

申请了一段空间,实际大小也就是sizeof(test)的大小,然而却调用了delete[]来释放,这里变可是会把指针往前偏移4个字节,所以后边free的时候自然会出错!同理,test *p2 = new test[10]; //delete p2; //free p2; 也是类似。而后面我将构造函数注释掉后,由于没有了构造函数,编译器便无需知道调用多少次析构函数,也就不会多开辟4个字节空间用来保存对象个数,那么也就不会出错了。

总结一下:

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


定位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 0