动态内存管理:malloc和free以及new和delete的联系与区别
一、 C/C++中程序内存区域划分:
- 内核空间:Linux系统对自身进行了划分,一部分核心软件独立于普通应用程序,运行在较高的特权级别上,它们驻留在被保护的内存空间上,拥有访问硬件设备的所有权限,Linux将此称为内核空间。
- 栈:又叫堆栈,非静态局部变量和函数参数及返回值都在栈上存储,栈是重上往下增长的。
- 内存映射段:是高效的I/O映射方式,用于装载一个共享的动态内存库,用户可以使用系统接口创建共享共享内存,做进程间通信,
- 堆:用于程序运行时动态分配内存,堆是从下往上增长的。
- 数据段:存储静态数据和全局变量。
- 代码段:可执行时的代码和只读常量。
具体如下图:
二、C语言中动态分配内存管理方式:
- malloc:
原型:
void *malloc(int byte_size);
作用:动态开辟byte_size个字节的内存空间,不进行初始化,返回指向此内存的指针,此指针所指数据类型没有确定,需要强转。
举例:
int *p=(int *)malloc(5*sizeof(int));
例解:动态开辟5*4=20个字节大小的空间,返回空间首地址指针并强转为int *型后赋予指针变量p。
注意:malloc只开辟空间,不初始化,即只将此空间由未占用状态变为已占用状态,空间内存储的具体数据未指定改变。函数返回值是空间首地址,上例中赋给了p变量。
2.calloc:
原型:void *calloc(int n,int type_byte_size);
作用:动态开辟n*type_byte_size个字节,将每个字节均初始化为ascii码0,返回指向此内存的指针,此指针所指数据类型没有确定,需要强转。
举例:int *p=(int *)calloc(5,sizeof(int));
例解:动态开辟5*4=20个字节大小的空间,其中每个字节均赋初值0,返回空间首地址指针并强转为int *型后赋予指针变量p。
注意:calloc在malloc的基础上将空间按字节初始化为ascii码0,且其参数有两个,两参数之积为空间总字节数。
- realloc:
原型:
void *realloc(void *p,int byte_size);
作用:对空间进行调整,若p为空,则该函数与malloc相同,若p所指空间连续大小(单位字节)大于byte_size,则从首地址开始连续地扩充开辟p所指空间至byte_size字节,不对空间再次赋值,将空间地址指针返回;若p所指空间连续大小小于byte_size,则新开辟byte_size字节大小的空间,将p指向的空间内的数据复制进新空间,之后释放p所指空间(此时p为NULL),返回指向新空间的指针,此指针所指数据类型没有确定,需要强转。
举例:
int *p=(int *)calloc(5,sizeof(int));
p =(int*)realloc(p,10*sizeof(int));
例解:首句中p为5*4=20字节的空间指针并按字节初始化为ascii码0,(int )强转后才限定了指向空间的每个元素为int型。后句将p所指空间扩充为104=40字节的空间指针,未对其二次赋值,故此时p[0]p[4]为0,p[5]p[9]未初始化。
注意:realloc的第一个参数必须是动态开辟的地址,不能是静态定义的数组的地址,结构体数组也不行。
4.free:
C 库函数 void free(voidptr)* 释放之前调用 calloc、malloc 或 realloc 所分配的内存空间 。
ptr-- 指针指向一个要释放内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果传递的参数是一个空指针,则不会执行任何动作。
该函数不返回任何值。
下面的实例演示了 free() 函数的用法:
#include <stdio.h>
int main()
{
char *str; /* 最初的内存分配 */
str = (char *) malloc(15);
strcpy(str, "jikexueyuan");
printf("String = %s, Address = %un", str, str); /* 重新分配内存 */
str = (char *) realloc(str, 25);
strcat(str, ".com");
printf("String = %s, Address = %un", str, str); /* 释放已分配的内存 */
free(str); return(0);
}
让我们编译并运行上面的程序,这将产生以下结果:
String = jikexueyuan, Address = 355090448String = jikexueyuan.com, Address = 355090448
三、C++内存管理方式:
- new/delete操作内置类型:
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[3];
delete ptr4;
delete ptr5;
delete[] ptr6;
}
注意:申请和释放单个元素空间,使用new/delete操作符,申请和释放连续元素空间,使用new[]/delete[]操作符。
2. new/delete操作自定义类型:
class Test
{
public:
Test()
: _data(0)
{
cout<<"Test():"<<this<<endl;
}
~Test()
{
cout<<"~Test():"<<this<<endl;
}
private:
int _data;
};
void Test2()
{
// 申请单个Test类型的空间
Test* p1 = (Test*)malloc(sizeof(Test));
free(p1);
// 申请10个Test类型的空间
Test* p2 = (Test*)malloc(sizoef(Test) * 10);
free(p2);
}
void Test2()
{
// 申请单个Test类型的对象
Test* p1 = new Test;
delete p1;
// 申请10个Test类型的对象
Test* p2 = new Test[10];
delete[] p2;
}
注意:在申请自定义类型空间时,new会调用类的构造函数,delete会调用类的析构函数,malloc和free不会调用。
- new和delete的实现原理:
a、如果申请的是内置空间:
那么new/delete,malloc/free就没有太大区别。不同的地方主要有如果malloc申请空间失败,则会返回NULL,所以在用之前要进行判空处理。而new操作符申请空间时,如果失败,就会优先进行调整,若没有调整操作,就会抛出异常。
b、如果申请的是自定义空间:
new的原理为:
首先调用operator new函数申请一个空间,而operator new函数则采用循环的方式,用malloc来进行申请空间,若申请空间失败,就会优先进行调整操作,直到申请成功,若没有调整操作,就会抛出异常。(注意此时并没有申请成功,只是相当于申请了以个大小相同的空间而已,此时该对象是不完整的)所以最后进行的操作是在申请的空间上执行类的构造函数,完成对象的构造。
delete的原理为:
首先为了防止内存泄漏,在该空间上执行类的析构函数,完成对象资源清理工作,最后调用operator delete函数,释放该对象的空间。
new[N]的原理为:
调用operator new[] 函数,而在operator new[]中实际调用operator new函数完成对N个对象空间的申请操作,最后在申请的空间上执行N次构造函数,完成对N个对象的申请。
delete[]原理为:
在申请的空间上执行N次析构函数,完成对N个对象的资源清理工作,最后在调用operator delete[] 函数,而在operator delete[]函数中实际调用operator delete函数完成对N个对象空间的释放回收操作。
四、malloc/free与new/delete的区别:
malloc/free | new/delete |
---|---|
是标准库内的函数 | 是关键字,不是函数 |
malloc申请的空间不会初始化 | new出来的空间可以初始化 |
malloc申请空间时,需要手动计算申请空间的大小并传递 | new不需要,只需在后面跟空间的类型 |
malloc的返回值为void*,所以在使用时要进行强转操作 | new后跟的就是空间的类型 |
malloc申请空间失败会返回NULL,所以使用时需要判空 | new申请空间失败会抛出异常 |
在申请自定义空间时,malloc/free只负责开辟空间,和释放空间,不会调用构造函数和析构函数 | new会在申请空间后调用构造函数完成对象的初始化,delete会在释放空间前调用析构函数完成资源清理工作。 |