您的位置:首页 > 其它

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

2016-06-02 19:41 411 查看

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大小的空间,将原有的数据重头到尾拷贝到新内存空间,然后将原空间释放,最后返回新分配空间的首地址。

注意:

realloc失败的时候返回NULL;

realloc失败的时候,原来的内存空间不改变,不会释放也不会移动;
如果size为0,效果等同于free,只对指针所指内存进行释放;对于二级指针**a realloc时,只会释放一维,注意使用时谨防内存泄漏;
传递给realloc的指针必须是先前通过malloc、calloc、realloc申请的;
当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个函数使用完后一定要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的区别和联系

它们都是动态管理内存的入口
malloc/free是C/C++标准库的函数,new/delete是C++操作符
malloc/free只是动态分配内存空间/释放空间。而new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理(清理成员)
malloc/free需要手动计算类型大小且返回值会void*,new/delete可自己计算类型的大小,返回对应类型的指针
对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free
它们都需要各自配对使用
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: