目录
一、内存的划分
1.各个内存区域
2.小练习
二、C++的动态内存管理
1.new和delete的引入
2.new和delete的使用
3.内存泄漏
三、深入了解new和delete关键字
1.operator new与operator delete函数
2.定位new(了解)
程序的运行需要内存,对于我们以前常用的32位操作系统,指针变量的大小为四字节,地址个数一共有2的32次方个,每一个字节加起来就是4G;而对于我们现在常用的64位操作系统,指针变量的大小为八字节,地址个数一共有2的64次方个,每一个字节加起来理论上是17179869184G,也就是16777216T,但是我们现在使用的电脑普遍是8G或者16G内存,所以内存空间对于地址的利用并不完全。
我们为了有效地使用内存,避免程序读写遇到空间上的冲突。就需要将内存划分为不同的区域,主要包括:内核空间、栈、堆、内存映射段、数据段、代码段。
注意这里格子的大小不代表每个区域空间的大小。
根据下图中的代码选择数据的储存位置
(1)选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____ staticGlobalVar在哪里?____
staticVar在哪里?____ localVar在哪里?____
num1 在哪里?____
char2在哪里?____ *char2在哪里?___
pChar3在哪里?____ *pChar3在哪里?____
ptr1在哪里?____ *ptr1在哪里?____
(2)填空题:
sizeof(num1) = ____;
sizeof(char2) = ____; strlen(char2) = ____;
sizeof(pChar3) = ____; strlen(pChar3) = ____;
sizeof(ptr1) = ____;
选择题答案:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?C(全局变量) staticGlobalVar在哪里?C(静态变量)
staticVar在哪里?C(静态变量) localVar在哪里?A(局部变量)
num1 在哪里?A(数组名是首元素地址,也在栈区)
char2在哪里?A(数组名是首元素地址,也在栈区) *char2在哪里?A(数组内容储存在栈区)
pChar3在哪里?A(地址储存在栈区) *pChar3在哪里?D(abcd是一个常量字符串,pChar3指向代码段内的这个常量字符串)
ptr1在哪里?A (地址储存在栈区) *ptr1在哪里?B(内容在堆区)
填空题答案:
sizeof(num1) = 40; (整个数组大小)
sizeof(char2) = 5(还有一个\0); strlen(char2) = 4;(不包含\0)
sizeof(pChar3) = 4/8;(指针都是4/8字节) strlen(pChar3) = 4;(不包含\0)
sizeof(ptr1) = 4/8;(指针都是4/8字节)二、C++的内存管理方式
在C语言中,我们主要用malloc、calloc、realloc三个函数开辟堆区空间,用free释放空间。
在C++中又引入了new和delete进行内存开辟与释放。
#include
using namespace std;
int main()
{
int* p1 = (int*)malloc(sizeof(int));
free(p1);
int* p2 = new int;
delete p2;
return 0;
}
上下两行代码起到的作用是一样的,上下都是开辟int类型大小的空间用p1和p2指针维护,然后释放空间。
new和delete的使用对于内置类型其实没有太多的优化,主要对于类类型申请空间更加方便。
#include
using namespace std;
class Date
{
public:
Date(int year = 2000, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
void Dateprint()
{
cout << _year << endl;
cout << _month << endl;
cout << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
int* p1 = new int;//堆区申请一个int类型的空间
delete p1;//释放空间
int* p2 = new int(1);//堆区申请一个int类型的空间并初始化为1
delete p2;//释放空间
int* p3 = new int[3];//开辟一个整型数组,可以储存三个元素
delete[] p3;//释放空间,注意释放数组要使用delete[]
Date* arr = new Date[10];//开辟一个储存十个Date元素的数组,此处相当于调用了十次构造函数
delete[] arr;//释放空间,使用delete[],[]内可以写调用几次析构函数,但是不写编译器也可以自己识别
//但是注意:变量->delete,数组->delete[],一定要前后对应,否则将产生内存泄漏
return 0;
}
内存泄漏是C/C++程序中最常见的错误,可以说防不胜防。
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制(也就是说我们没有指针维护块空间了),因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
内存泄漏分类:
内存泄漏除了我们在写代码的过程中规避,还有两种方式检查
其实是有的在vs下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks() 函数进行简单检测,当我们调试到该位置,在输出窗口:该函数只报出了大概泄漏了多少个字节,没有其他更准确的位置信息。
这两个全局函数实现了new和delete两个关键字,其实这里的operator并不代表运算符重载,因为在C语言中也不存在这两个关键字,这两个函数很特殊,记住它们不是运算符重载。
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size byte
svoid *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
operator new 实际也是通过malloc来申请空间,如果 malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。
operator delete 最终是通过free来释放空间的。
总之,new和delete是C语言中malloc和free的封装,即使我们知道它们的底层实现原理但是我们也不要使用这两个函数。
new/delete和malloc/free的对比:
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式: new (place_address) type或者new (place_address) type(initializer-list) place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景: 定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
// 定位new/replacement new
int main()
{
A* p1 = (A*)malloc(sizeof(A));
//p1现在指向的只是与A对象相同大小的空间,还不能算是一个对象
new(p1)A;
//对这一块空间调用A的构造函数
p1->~A();
//p1是A*指针,指向一个已经初始化好的变量,调用内部析构函数
free(p1);
//释放空间
return 0;
}
C++内存管理结束