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

本文详细介绍了C/C++中动态内存管理的相关知识,包括malloc、calloc、realloc、free等函数的使用方法及其注意事项,对比了C++中new/delete运算符与C语言中内存管理函数的不同之处。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 ()
{
// 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的区别和联系

  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. 它们都需要各自配对使用



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fireplusplus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值