C++的动态内存管理

来源:互联网 发布:武汉淘宝图片拍摄 编辑:程序博客网 时间:2024/06/06 07:41

一、C语言中malloc、calloc、realloc的不同

一、malloc
函数的原型是:extern void *malloc(unsigned int num_bytes);
头文件是#include <malloc.h>
函数的功能是:分配num_bytes的字节块;向内存申请空间,申请成功返回指向内存分配的指针,申请不成功返回NULL;
函数的声明是:void* malloc(size_t size);sizeof计算出的字节的大小
这里的返回值用的是void*,可转换成任何类型的指针。如果申请成功则返回指向这块内存空间的指针,否则返回NULL;malloc对于内存不进行初始化,申请好之后给它分配的是随机值。
举例如下:

int *p;p=(int *)malloc(sizeof(int)*10);//连续分配10个整型的空间;int*ret;ret=(int *)realloc(p,20);

函数的参数为 sizeof(int)指明为一个整形数据的大小,*10是想连续的分配空间的大小;
如果写成p=(int *)malloc(1),那么只会分配一个字节的大小。而一个整数为4个字节,就会放不下。malloc申请的空间最后用free来释放
二、calloc
函数的原型是:void *calloc(size_t n,size_t size);在系统的动态存储区内分配n个大小为size的连续空间,如果分配不成,则返回空,
它与malloc最大的区别就是在申请空间后,对所申请的空间进行了初始化(所有的初始值为0)。也是用free来释放空间。举例说明:

include <malloc.h>char *ret;ret=(char*)calloc(10,sizeof(char));连续申请10char类型的空间,free(ret);//释放空间

如下验证到底是不是进行了初始化:

include <iostream>include <malloc.h>using namespace std;int main(){    int i;    int *p = NULL;    p = (int*)calloc(10,sizeof(int));    for (i = 0; i < 10; i++)    {        cout << p[i] << endl;    }}

三、realloc 动态的内存分配
函数原型是:extern void* realloc(void*mem_address, unsigned int newsize);
功能:改变mem_address所指的原始空间的大小为newsize,原始空间的数据并不会被改变;
说明:如果分配成功则返回新的指向内存大小的指针,否则返回空,在新的内存大小的空间大于原始内存时,新分配的内存不会被初始化
但是新分配的内存的大小小于原始空间内存的大小,就会导致数据的丢失;
分配内存时应注意的问题:
①如果mem_address为空,则直接分配newsize个字节即可。但是如果newsize为0.则这个空间就会被free掉。
②如果分配的时候newsize比原来的内存空间大的时候,则会重新申请一块空间(前提是系统有足够的空间申请,否则原来的内存不做改变),然后将原来空间的数据复制过去,此时数据发生了移动,最后返回的是新空间的地址。
③如果原来的内存还有剩余,并且能满足对于新分配内存的需要,则新的内存就是:原有的内存+剩下的内存,返回的还是原来指向空间的指针。
④传递给realloc的空间的地址要是malloc、calloc分配好的内存空间的地址。举例如下:

int *p;p=(int *)malloc(sizeof(int)*10);//连续分配10个整型的空间;int*ret;ret=(int *)realloc(p,20);

2、常见的内存泄露—–>扩展:C/C++如何进行内存泄露检测?

1>什么是内存泄露
我们在编写程序的过程中,由于疏忽或者错误忘记释放掉已经不用的内存,它并不是只是物理上的消失,应用程序分配到某快内存后,但是由于设计错误,失去了对内存的控制,就会造成内存的浪费。
2>对于C/C++来讲,我们通常关心两种泄露:
①堆内存泄露:我们使用malloc.calloc.realloc所在堆上分配的一块内存,完成之后必须用delete/free来释放掉这块的内存。如果没有释放,那么这块空间以后就不会在被用到,就会造成栈内存泄露(Heap Leak)
②系统资源泄露:程序使用系统分配的资源没有使用相应的函数释放掉。导致系统资源的浪费,有时候会导致程序的性能降低,运行不稳定。
3>常见的内存泄露
①单例造成的内存泄露
由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。
②非静态内部类创建静态实例造成的内存泄漏
③Handler造成的内存泄漏
④线程造成的内存泄露
⑤资源未关闭造成的内存泄漏
⑥使用ListView时造成的内存泄漏
⑦集合容器中的内存泄露
⑧WebView造成的泄露
4>C/C++如何进行内存泄露检测?
因为内存泄漏是在堆内存中,所以对我们来说并不是可见的。通常我们可以借助MAT、LeakCanary等工具来检测应用程序是否存在内存泄漏。
1、MAT是一款强大的内存分析工具,功能繁多而复杂。
2、LeakCanary则是由Square开源的一款轻量级的第三方内存泄漏检测工具,当检测到程序中产生内存泄漏时,它将以最直观的方式告诉我们哪里产生了内存泄漏和导致谁泄漏了而不能被回收。
5>我们如何避免内存泄露
我们应该养成一种良好的习惯:调用了malloc/new等申请内存之后,没有用相应的free/delete释放掉。保证每个malloc都有对应的free,每个new都有对应的deleted

3、malloc/free和 new/delete的区别

C++中我们通常用new来申请空间,用delete来释放空间。new[]来申请连续的空间,delete[]进行释放。而且他们是匹配使用的。
举下面的例子

int*p1=new int;//动态分配一个4个字节(一个int)的空间int*p2=new int(10);//动态分配一个4个字节(一个int)的空间。并初始化成10;int*p3=new int[3];//动态分配12个字节(3个int)的空间

malloc/free和 new/delete的相同点:都可以申请内存和释放内存
不同点:
①本质区别
malloc/free是库函数,而new/delete是运算符;malloc/free只是动态的分配内存空间、释放内存空间。但是new/delete除了分配内存空间外还会调用构造函数和析构函数进行初始化和清理;
举例:现在有一个类:AA
AA *p2=new AA;它的底层是先开辟空间用operator new(它的函数内部其实也用的是malloc),最后再调用析构函数进行初始化。、
释放内存的时候 free AA;也是先调用operator delete这个函数释放,在调用析构函数。
不同还体现在对于自定义的一个数组:
malloc只是单纯的开辟空间,而对于new来说
AA* p2=new;//–>operator new->malloc–>构造函数
执行的过程是①先是开辟空间②构造函数初始化
如果是一个数组:
Array p3=(Array )malloc(sizeof(arry));//用malloc开辟空间
Array *p4=new Array;//用new 来开辟空间,下面图片给出描述:
这里写图片描述

②用法上的不同
下面是malloc函数的原型:

void *malloc(size_t size)

①malloc它只关心分配多少个字节,并不关心分配的类型,我们只要计算出它的总的长度即可。
②它的返回值void*可以任意转换成任何类型的指针。
malloc申请的空间要用free来释放
free函数的原型是:void free( void * memblock );
它只关心所要释放空间指针指向的首地址就可以了。如果free的指针是NULL指针,则它free多少次程序都不会出错,但是如果不是空指针,连续释放两次就会导致程序出错。
new
new写起来比malloc要简单的多,
int *p=new int[10];
new是一个关键字,它内置了sizeof、类型转换、类型检测的功能,从某种程度上来说,new在创建对象的同时也进行了初始化。创建多个对象:

int *p=new int[100];delete[] p;

释放的时候用delete[],[]千万不能少,少了就会漏掉99个对象。
那么总结起来:
①new能自动的计算字节数并返回对应类型的指针,而malloc需要手工的计算字节数,而且它返回是void*,需要根据自己需要的类型进行转换。
②new的类型是安全的,但是malloc不是;

int *p=new double[2];//编译的时候出错int *p=malloc(sizeof(int)*2);//编译的时候不会报错

③malloc/free需要库文件支持,而new/delete不需要;
1>对于开辟和释放内存必须要匹配的问题,做如下说明:

int *p1=(int *)malloc(sizeof(int));int *p2=new int;free(p2);delete(p1);

这种情况是可以的(内置类型),释放的时候程序不会出现问题,因为他们的调用机制是一样的,但是最好类型匹配。
2>不是内置类型程序也不会崩溃掉,调用机制也基本上是相同的。
比如说是这种

AA*p3=(AA*)malloc(sizeof(AA));AA*p4=new  AA;

3>这种情况下是必须要进行匹配的

AA *p5=(AA*)malloc(sizeof(AA)*10);AA *p6=new AA[10];free(p6);delete[] p5;

这种释放方式底层就会出现问题,对于p5来说,就是正常的开辟了连续10个字节的整数的空间,但是对于p6来说,它的底层是调用operator new->malloc,返回的时候malloc会多返回4个字节,但是p6在四个字节之后的字节。如下图所示:
这里写图片描述
那么多开辟的这四个字节是干什么用的呢?它其实是为存取数据的个数用的,因为它要给析构函数做准备,就比如说在调用析构函数的时候,delete[] p6;它并不知道要析构多少次,指针往前走,取出这四个字节里存放的数据的个数,再根据这个数字来确定释放多少个空间。
那么问题来了:是不是任何时候都会开辟空间呢?
不是的,如果是内置类型(int类型就不会多开开辟空间),如果是自定义类型就会开辟空间,但这所有的前提都是在有析构函数。

4/new/new[]/delete/delete[]的执行原理图

这里写图片描述new/new[]/delete/delete[]的执行原理图

5、定位new表达式

在已经分配的原始空间的内存中调用构造函数初始化一个对象。
new(place_address)type( initializer-list)
place_address是一个指针,initializer-list是初始化列表。

6、一个类如何只在堆上创建对象

在用类创建对象是有两种:①静态创建:编译器在栈上为对象分配内存。
② 动态创建(new):编译器在堆上为对象分配内存。
那么如何只在堆上创建呢:不论是静态创建还是动态创建都需要调用构造函数为对象进行初始化,那么对构造函数做任何处理是行不同的,那么编译器对于析构函数在调用之前都会检查,它是不是可以访问,如果是私有就只能在堆上创建空间,那么解决的办法就是将析构函数设为私有;

7、一个类如何只在栈上创建对象

在栈上创建对象与堆上恰好相反,只要让new不能用就可以了,我们都知道new的底层都是调用 opreator new()来实现的,那么我们把这个函数给它写出来,但是定义它为私有的,那么new就不能调用,就只能静态创建,就是实现了在栈上创建了对象。

8、malloc的实现方式

内存块是靠堆来实现的,malloc函数的实质体现在,它会将可用的内存块连接长一个长长的空闲链表,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。

原创粉丝点击