内存划分
对于一个程序,在运行期间,其中的函数,变量都要在内存对应的区域申请空间,我们将内存划分为栈、堆、静态区(数据段)、常量区(代码段)
栈:主要是存放局部数据和函数参数,在内存中连续存储的区域,地址由低到高
堆:动态申请数据,new(malloc)分配的内存块,需要delete(free),如果没有delete(free)在程序退出有可能被系统自动回收
静态区(数据段):全局变量、静态变量的存储区域。c中分为初始化和未初始化区域,c++中是同一块内存区域
常量区(代码段):常量数据,特殊的存储区域,存放常量,不允许修改
代码区:存放函数体的二进制代码,由操作系统进行管理
管理方式
- 堆:由程序员手动处理,申请和销毁都要手动,没有delete(free)容易出现内存泄漏
- 栈:是由编译器自动管理实现,不会穿出现内存泄漏
碎片问题
对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出。
生长方向
- 堆:生长方向向上的,也就是向着内存地址增加的方向
- 栈:生长方向向下的,也就是向着内存地址减小的方向增长
分配效率
- 栈:栈是机器系统提供的数据结构,是由专门的寄存器存放栈的地址,压栈出栈都有专门的指令来执行,所以栈的效率比较高
- 堆:堆的实现是通过程序员手动new/delete来申请和释放空间,通过new调用复杂的函数来去堆中寻找合适大小的内存空间,如果大小不够,就会调用系统功能去增加程序数据段的内存空间,这样就可以分配到合适的内存空间,
C语言中的动态内存管理
主要函数为:malloc/calloc/realloc/free
通过函数malloc/calloc/realloc动态申请空间,free来释放空间,防止内存泄漏
//使用malloc/calloc/realloc
int main()
{
//动态申请空间
//malloc是在堆中直接申请空间
int* p1 = (int*)malloc(sizeof(int) * 10);//一个参数,申请空间的大小为10*sizeof(int)
//calloc是在堆中直接申请空间,初始化为0
int* p2 = (int*)calloc(10, sizeof(int));//两个参数,第一个为几个空间(个数)第二个参数:每一个元素空间大小
//realloc申请空间有两个方式
//1.在原有空间p2基础上增加(减少)空间到20个int类型空间 返回旧地址
//2.对于p2原有空间的内容进行拷贝,然后单独直接从堆中申请20个int类型的空间 返回新地址
int* p3 = (int*)realloc(p2, sizeof(int) * 20);//两个参数
free(p1);
free(p2);
//free(p3);//对于p2和p3只能释放一次(防止多次释放报错)
return 0;
}
malloc/calloc/realloc函数的区别
区别
- 相同点
- 都是从堆上申请空间
- 都需要对于返回值进行判空
- 都需要用户手动free释放空间
- 返回值类型相同(void*)需要类型强转
- 底层实现都是一样的,都需要开辟出来多余的空间,用来维护申请的空间
- 不同点
- 函数名和参数类型不同
- calloc会将元素初始化为0,其他两个不会
- malloc申请的空间进行初始化必须使用函数memset
- realloc对于已经存在的空间进行调整,有两种方式,返回原地址or新地址
realloc是最为麻烦的一种,对于返回新地址还是旧地址,我们可以这样判别
void*realloc(void * p,int newsize) p地址的size 为oldsize
old>new
- 是将原有地址的空间变小,变成newsize大小,返回旧地址
old<new
- oldsize略小于newsize的大小,那么就是在原有地址的基础上延申空间,返回原有地址的首地址
old<<new
- newsize远大于oldsize的时候,先开辟newsize大小的新空间,将旧空间的内容拷贝到新空间中,释放旧空间,返回新空间的首地址
C++的内存管理
延续C语言,并实现了new和delete的方式来开辟空间
//对于new和delete的方式来实现动态内存管理
//new和delete
class A
{
public:
A(int a = 0,int size)//构造函数
: _a(a)
{
cout << "A():" << this << endl;
array=(int*)malloc(sizeof(int)*size);
}
~A()//析构函数
{
cout << "~A():" << this << endl;
}
private:
int* array;
int _a;
};
int main()
{
A* pa=new A();
delete pa;
A* pb=new A[10];
delete[] pb;//new类型为new [] 所以delete的时候 delete[]
A* pc=new A(10);//对于这个A类型进行初始化,初始化的时候,必须对应A类型的构造函数参数个数
delete pc;
A* pd=new A[10]{};//这样就是创建一个A类型的数组有10个元素A,初始化为0(表示_a=0)
delete[] pd;
return 0;
}
new和delete的出现的优势为,比malloc简单,不需要强制类型转换,而且很方便
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]
注意:new和delete会分别调用构造函数和析构函数,malloc/free不会调用
malloc和new的区别
1.动态申请内置类型的数据:new和malloc是没有什么本质的区别的
2.动态申请自定义类型的数据:new和malloc除了用法上面,new和delete会调用构造函数(初始化)和析构函数(清理)
new和delete的实现原理
new和delete是为了优化malloc产生的,通过new的反汇编可知道,使用new会调用函数operator new,然后进入这个函数之后,里面有malloc函数,所以实际上还是malloc申请的空间
//首先先认识operator new
new 会进行开空间+构造函数 delete 析构函数 + 释放空间
malloc 开空间(直接) free 释放空间
先开空间是为了给实例化对象一个位置,然后使用构造函数为初始化对象,最后先析构函数,是将自定义类型中的一些动态申请的变量,进行free释放空间,防止内存泄漏,最后释放对象的空间
开空间(自定义类型):operator new → malloc
释放空间(自定义类型):operator delete → free
自定义类型new/delete的顺序为:开空间→构造函数→析构函数→释放空间
定位new表达式
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象,多用于内存池中,在一般的场景下不会轻易使用,内存池分配出的内存没有初始化
语法:new(place_address)type 或者 new(place_address)type(initializer_list)
//对于第一种语法是,type类型有默认构造函数/全缺省构造函数
//第二种语法是为非默认构造/非全缺省函数
class A
{
public:
A(int a)
:_a(a)
{
cout<<"A(int a)"<<endl;
}
private:
int _a;
};
int main()
{
A a(1);//创建的方式1
A* pa=(A*)malloc(sizeof(A));//这个时候的pa指向的不过是与A对象大小一致的一段空间,还不能算是一个对象,因为构造函数此时还没有执行
new(pa)A(1);//定位new实例化对象pa
return 0;
}
malloc/free和new/delete区别
malloc/free和new/delete都是从堆中申请空间,且需要手动释放
对于内置类型来讲,只是申请的方式不同,但是对于自定义类型, new会调用构造函数初始化对象(malloc不行),delete会调用析构函数释放对象资源(free不行)
*********************************************************
不同点:
1.malloc和free都是函数,但是new和delete是操作符(所以才有operator new/delete)
2.malloc申请的空间不会初始化,但是new会调用构造函数进行初始化
3.malloc申请空间的时候,返回NULL所以是需要判空的,但是new会抛出异常(异常是可以抛出的,程序并不会停止)不用判空
4.malloc在申请空间的时候,需要手动计算空间大小并传递,new只需要跟上对应的类型即可,所以显得比较简单,不用强转和sizeof的使用,如果是多个元素,new只需要知道要申请多少个元素即可,只是new[]和delete[]
5.malloc返回值为void*,所以需要强转到需要的类型,new不用
6.自定义类型时,new/delete会分别调用构造函数和析构函数进行初始化和对于对象资源的释放
内存泄漏
在C语言的学习阶段我们就认识了内存泄漏这个名词,那么到底什么是内存泄漏,危害又是什么呢?
定义:
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况
内存泄漏主要是因为设计错误,对于一些没有使用的空间没有及时释放,内存泄漏并不是物理上的消失(不然多来几次,电脑就废啦),而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
至于有什么危害,我们来看下面的这段代码
int main()
{
int* a = new int[1024 * 1024*100];
return 0;
}
所以我们应该防止内存泄漏的发生,主要的危害是对于需要长期运行的程序,比如一些操作系统、服务后台等,会慢慢导致系统变慢,最后崩溃
我们采取两个方法来规避内存泄漏的问题,1.使用智能指针提前预防,2.程序运行之后,使用第三方测试软件进行检测
总结
C++的内存管理,对于内存区间进行划分,分为四个区域,栈、堆、静态区、常量区、存储的内容以及功能讲解,将malloc/calloc/realloc进行区别对比,以及引入new/delete这一C++动态申请内存的操作符,将new/delete和malloc/free进行对比分析区别,然后引入new/delete的原理,最后引入定位new表达式,对于内存泄漏进行讲解。