一、c/c++程序内存区域划分
c和c++的内存区域划分是十分相似的,因为c++是完全兼容c语言,是c语言的面向对象的升级版。
接下来看如下图:
程序的内存区域被划分成6个区域。内核空间、栈、内存映射段、堆、数据段、代码段。
下面是对相关内存区域名词解释:
- 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口 创建共享共享内存,做进程间通信。
- 堆用于程序运行时动态内存分配(malloc/new),堆是可以上增长的。
- 数据段--存储全局数据和静态数据(static)。
- 代码段--可执行的代码/只读常量(const)。
二、c语言中的动态内存管理方式
常见的函数有:malloc/calloc/realloc/free
看下面这一段代码我们来进行剖析和回顾
#include<iostream>
using namespace std;
int main()
{
//malloc 动态申请4个字节的空间
int* p1 = (int*)malloc(sizeof(int));
// 释放内存空间
free(p1);
// calloc 给定初始化数据类型的个数 并都初始化为0
int* p2 = (int*)calloc(4, sizeof(int));
//realloc 在原有的分配的动态内存上进行扩容 (原地扩容/异地扩容)
int* p3 = (int*)realloc(p2, sizeof(int) * 10);
//释放内存
free(p3);
return 0;
}
1、为什么可以不用free(p2),而只释放p3即可?
因为这跟realloc的机制和动态内存的释放机制有关,动态内存的释放我们要从头开始释放整个开辟的连续的空间,如果调用realloc进行扩容发现后面还有足够的内存空间可以进行扩容,那么就是原地扩容,p3这个指针就是代表p2动态开辟的内存+realloc出来的内存空间的头,所以就只要释放p3。当realloc是进行的异地扩容那么就都要释放。
2、那么我先释放p3,再释放p2,可以吗?
原地扩容释放p3后,再释放p2,释放p2的时候是找不到地址的,会出现报错。
3、malloc/calloc/realloc的区别?
malloc、calloc、realloc三者主要区别在于分配内存的方式和初始化。malloc只分配内存,不进行初始化;calloc分配内存并初始化为0;而realloc则重新调整已经分配内存块的大小。另外,realloc() 函数需要传入先前分配的内存地址,而 malloc() 和 calloc() 则不需要。需要注意的是,这些函数都可以引起内存泄漏,必须在使用完后释放分配的内存区域。
三、c++中的动态内存管理方式
c++中也还是可以用c语言的内存的管理方式,但是在面向对象过程中太过于繁琐,所以c++建立了一套新的内存管理方式:通过new和delete操作符进行动态内存管理。
实例代码:
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[10];
delete ptr4;
delete ptr5;
delete[] ptr6;
}
在上面代码里面可以看到int(10),这个很熟悉。这个是像是对象的初始化。这里我们可以探究一下new这个操作符是不是给int进行了初始化。
通过调试如下图:
可以看出来new int(10) ,是进行了初始化的,那么问题来了,new int会不会进行初始化呢,我们知道构造函数和拷贝构造是对内置类型不做处理的。那么如果进行了初始化的话,就是一个随机数。
下面是代码调试:
我们可以看到p2是被初始化为-842150451这个随机数了,还是可以佐证new这个操作符进行了那些优化,可以进行对对象的初始化操作。
new对对象的实例化
这里注意:对对象数组的管理,因为是申请和释放连续的空间,使用new[]和delete[]。
上面的代码实例展示的是对内置类型的内存管理
下面我将介绍对自定义类型的对象内存管理
class A
{
public:
//初始化列表
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
//还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A)*10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}
在自定义类型中也是如此,会在new的时候主动的去调用构造函数通过初始化列表进行初始化。在delete的时候先主动调用析构函数对资源进行清理。再释放空间。
四、c&c++动态内存管理对比
4.1malloc/new&&free/delete
4.1.1 在内置类型对象的初始化没有区别
我们知道malloc在申请int类型的内存空间的时候,是只申请空间没有进行初始化,同样的new操作符在申请空间的时候是进行调用构造函数,构造函数对内置类型是不进行处理的,也就是和malloc没有区别。
4.1.2 new和delete特性
用new进行自定义类型的对象创建是自动调用构造函数
delete 的时候对自定义类型的对象是自动调用析构函数
4.1.3 自定义数组初始化问题
当我new了一个类类型的数组的时候,再进行delete,会进行重复调用构造函数和析构函数
特别注意的是 自定义类型定义的数组,需要有默认构造,供其调用,否者会提示编译不通过
如图:
1、在构造函数给缺省值
#include<iostream>
using namespace std;
class A
{
public:
A(int a=10)
:_a(a)
{
cout << "A(int a)" <<this<< endl;
}
~A()
{
cout << "~A()"<<this << endl;
}
private:
int _a;
};
int main()
{
int* p1 = new int; //在堆上开了一个int类型的变量 然后在栈里面是一个p1 指向堆的这个变量
int* p2 = new int[3]; //在堆上开了三个int类型的变量、
delete p1;
delete[] p2;
A* p3 = (A*)malloc(sizeof(A));
A* p4 = new A(1);
free(p3);
delete(p4);
A* p5 = new A[10]; //可见这里的对象都被初始化为10
delete[] p5;
return 0;
}
2、c++11特性 在成员变量声明给缺省值 然后调用默认构造初始化
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
:_a(a)
{
cout << "A(int a)" <<this<< endl;
}
~A()
{
cout << "~A()"<<this << endl;
}
private:
int _a=10;
};
int main()
{
int* p1 = new int; //在堆上开了一个int类型的变量 然后在栈里面是一个p1 指向堆的这个变量
int* p2 = new int[3]; //在堆上开了三个int类型的变量、
delete p1;
delete[] p2;
A* p3 = (A*)malloc(sizeof(A));
A* p4 = new A(1);
free(p3);
delete(p4);
A* p5 = new A[10]; //这里也是全部被初始化为10
delete[] p5;
return 0;
}
3、如果没有默认构造,会进行报错,“没有合适默认构造可用”
4、解决方案
解决方案:主动进行初始化,在创建自定义类型对象的时候加上{},初始化
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
:_a(a)
{
cout << "A(int a)" <<this<< endl;
}
~A()
{
cout << "~A()"<<this << endl;
}
private:
int _a;
};
int main()
{
int* p1 = new int; //在堆上开了一个int类型的变量 然后在栈里面是一个p1 指向堆的这个变量
int* p2 = new int[3]; //在堆上开了三个int类型的变量、
delete p1;
delete[] p2;
A* p3 = (A*)malloc(sizeof(A));
A* p4 = new A(1);
free(p3);
delete(p4);
A* p5 = new A[10]{1,2,3,4,5,6,7,8,9,10};
delete[] p5;
return 0;
}
注意:在主动进行初始化的时候要注意如果是多参数的话,就不能用隐式类型转化了,要显示的写出来入
#include<iostream>
using namespace std;
class A
{
public:
A(int a,int b)
:_a(a)
,_b(b)
{
cout << "A(int a)" <<this<< endl;
}
~A()
{
cout << "~A()"<<this << endl;
}
private:
int _a;
int _b;
};
int main()
{
A* p5 = new A[4]{A(1,2),A(1,2),A(1,2),A(1,2)};
delete[] p5;
return 0;
}
4.2 使用原则
原则:一定要匹配使用
malloc 和free 对应
new 和 delete对应
malloc 和new除了用法上面的区别 ,还有一个重大的区别 就是new和delete会调用构造函数初始化,析构函数清理资源。
4.3 operator new 和 operator delete 函数
全局函数 ----来自库里面的函数
int main()
{
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
operator delete (p1);
delete p2;
A* p3 = (A*)operator new (sizeof(A));
A* p4 = new A(1, 2);
operator delete (p3);
delete (p4);
return 0;
}
总的来说,new 开空间+构造函数 (new开空间是不能直接调用malloc 因为c++需要去捕获异常)
malloc 开空间
delete 析构函数 + 释放空间
free 释放空间
4.3.1 new 和 delete的实现原理
operator new 和 operator delete是全局函数,在我们用new和delete的时候是调用的这个两个全局函数,来申请和释放空间。也就是说new和delete在底层申请空间和释放的时候还是调用的malloc和free。
operator new 的好处:面向对象语言处理失败,不喜欢返回值,更建议抛异常 。operator new 当申请的空间失败,会执行我们设定的保护措施,否者就是会抛出异常。
4.4 大内存运行对比
先用malloc试试
内存直接爆满接近6G~~~ 基本上malloc不会失败
但是当换到new
int main()
{
int* p1 = nullptr;
do
{
/* p1 = (int*)malloc(1024 * 1024);*/
p1 = new int[1024 * 1024];
cout << p1 << endl;
} while (p1);
return 0;
}
先是直接内存爆满然后黑屏,给我吓一跳,幸好是win系统下面是给我分配的虚拟内存(一种保护机制吧)然后抛出未经处理的异常
解决办法:一般是要用try ,catch来捕获异常
int main()
{
int* p1 = nullptr;
try
{
do
{
/* p1 = (int*)malloc(1024 * 1024);*/
p1 = new int[1024 * 1024];
cout << p1 << endl;
} while (p1);
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
五、 malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
1. malloc和free是函数,new和delete是操作符
2. malloc申请的空间不会初始化,new可以初始化
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。
六、内存泄漏
6.1什么是内存泄漏,内存泄漏的危害
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死。
最严重的是在服务器里面跑的程序每次泄漏一点内存,时间长了也不能发现,就是觉得变卡了。
6.2内存泄漏分类
- 堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
- 系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
6.3如何避免内存泄漏
工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。
2. 采用RAII思想或者智能指针来管理资源。
3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
总结一下:
标签:初始化,malloc,缓存,int,C++,内存,new,delete From: https://blog.51cto.com/u_15831056/6368962内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。