c++|内存管理

为什么c++要专门重新弄一套内存管理,c语言的内存管理是有什么缺点?我们主要管理的是内存的那块区域?看完这章你将知晓答案

C/C++内存分布

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
 static int staticVar = 1;
 int localVar = 1;
 int num1[10] = { 1, 2, 3, 4 };
 char char2[] = "abcd";
 const char* pChar3 = "abcd";
 int* ptr1 = (int*)malloc(sizeof(int) * 4);
 int* ptr2 = (int*)calloc(4, sizeof(int));
 int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
 free(ptr1);
 free(ptr3);
}

选择题一:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____ staticGlobalVar在哪里?____
staticVar在哪里?____ localVar在哪里?____
num1 在哪里?____

请问上面填什么?(从左到右从上到下)
CCCAA
因为全局变量和satic修饰的都在静态区里
普通的变量在栈上

选择题二:
char2在哪里?____ *char2在哪里?___
pChar3在哪里?____ *pChar3在哪里?____
ptr1在哪里?____ *ptr1在哪里?____
AAADAB

对于char2 abcd字符串确实是在常量区 但是赋值给 char2的时候又拷贝了一份 char2数组名只有两种情况表示的是整个数组1sizeof(数组名)2.&数组名 其他都表示首元素地址 这里表示首元素地址

指针没有特别修饰所以在栈上。 *char2代表a a也在栈上 pchar3是一个指针 在站上 *pchar 它指向的内容在常量区(字符串在常量区)上和char2不同的是 char2根据字符串重新拷贝了一份在栈上

在这里插入图片描述

区域名特征
动态开辟的才在堆上
一般的变量
数据段/静态区全局变量或者static修饰的变量
代码段/常量区常量、字符串

注:/表示是一个东西,前者是从操作系统的角度,后者是从语言的角度

strlen 和 sizeof的区别

strlen遇到\0就结束 sizeof则要算上\0

在这里插入图片描述

c语言动态内存管理方式

malloc

在这里插入图片描述
在这里插入图片描述
作用:分配空间但不初始化

参数:要分配空间的总大小
malloc returns a void pointer to the allocated space, or NULL if there is insufficient memory available.
返回值:开辟成功则返回一个void的指针,就是因为返回的是void的指针所以每次用的时候都要强制类型转换。如果可用的空间不足则返回NULL空指针

calloc

在这里插入图片描述
作用:分配一段空间,并且初始化为零

参数:多少个元素,每个元素的大小
返回值和malloc一样

realloc

在这里插入图片描述

作用:重新分配空间
参数:以前指向该空间的指针,和新空间的大小
返回值:分会3种情况 成功1:异地拷贝,返回指针被改变原先的空间被释放 成功2:就地扩容 原先的指针不改变 失败则返回空指针。

例题

void Test ()
{
int* p1 = (int*) malloc(sizeof(int));
free(p1);
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}

一般不需要 因为对于成功1异地拷贝这种情况原先的空间已经被free了,对于成功2就地拷贝这种情况free(p3)就是free(p2)所以也不需要 但是如果失败 不free(p2)会造成内存泄漏,这种概率非常的低

c++管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力比如自定义类型的初始化,而且使用起来比较麻烦自己还要算分配的空间大小还要强制类型转换,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

new/delete操作内置类型

c语言内存管理函数不能随意初始化而c++的new可以,好感度+1
new 和delete 不用手动检查 失败了抛异常这一点就秒杀c语言的内存管理函数了 好感度+++++

#include <iostream>
#include<string.h>
using namespace std;

int main()
{
	int* s = new int(1); // 开辟了一个空间
	int* a = new int[5];// 随机在栈开辟了五个int的空间
	int* b = new int[5] {1, 2, 3, 4,5}; // 开辟了五个空间并且赋初始值
	delete s;
	delete[]a;
	delete[]b;
	cout << " " << endl;


}

注意 new 和delete 要搭配使用 new 和 delete 一对 new [] 和 delete [ ] 一对

在这里插入图片描述

new/delete操作自定义类型

除了开空间还分别调用了构造函数和析构函数

完成了c语言的内存管理函数无法做到的一点 好感++

证明

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;
	A* p5 = (A*)malloc(sizeof(A) * 10);
	A* p6 = new A[10];
	free(p5);
	delete[] p6;
	return 0;
}

在这里插入图片描述

new 和 delete 的底层原理是什么呢?

new 和 delete 的底层原理

operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,
operator new 和operator delete是系统提供的全局函数,

operator new 和 operator delete的 用法

operator new 的用法 和malloc 一样
operator delete 的用法 和 free一样

int main()
{
	int* a = (int*)operator new (4*10);
	operator delete (a);
	return 0;
}

new在底层调用operator new全局函数来申请空间 + 构造函数初始化,delete在底层通过operator delete全局函数来释放空间+析构函数恢复初始值
证明:

在这里插入图片描述

operator new 的底层是封装malloc函数的
operator new 的用法 和 malloc函数一样

在这里插入图片描述

  1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对
    象空间的申请
  2. 在申请的空间上执行N次构造函数

在这里插入图片描述
delete 同理

构造函数里面有其他的new 会死递归吗?

在这里插入图片描述

不会因为 new 是一个操作符 先 operater new 开辟了stack的空间 完了才调用 a的new ,a 再去开空间 a调自己的构造函数。

delete的小细节

delete 是先析构还是先operate delete

class Stack
{
public:
	Stack()
	{
		a = new int[4];
		_top = 0;
		_capacity = 4;
	}
	~Stack()
	{
		free(a);
		_top = _capacity = 0;
	}
private:
	int _top;
	int* a;
	int _capacity;
};

int main()
{
	Stack *pa = new Stack();
	delete pa;
	return 0;
}

oprate new 开辟了_a _top _capacity的空间

在这里插入图片描述

如果先operate delete _a _top _capacity的空间不见了 则 _a开辟的内存无法释放
所以先 析构再 operate delete

在这里插入图片描述

为什么要new[ ] 对应 delete 【】

我们对于A类只有一个成员 int a 开辟10个类型的A 不是应该是 40吗16进制28 哪为什么汇编的结果是16进制 30 48呢?

在这里插入图片描述

原因在于我们delete的时候不知道要析构多少次 用额外的空间保存了这一信息

在这里插入图片描述
在这里插入图片描述

当我们把析构函数注释掉时结果又正常了 原因在于编译器生成了析构函数对于A类什么也没干 可以不用调用 也就可以不知道需要调用几次delete了所以就不需要保存额外的信息

在这里插入图片描述

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;

	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	int* a = new int[10];
	delete a;
	A* p2 = new A[10];
	delete p2;
	
	return 0;
}

delete a 会报错吗?不会 因为a是内置类型 相当于只调用了 malloc 和 free
delete p2 会报错吗?我们看一下运行结果

在这里插入图片描述

报错了 原因是什么呢?因为有构造函数 多申请了额外的空间而p2不是指向开头的 不能从中间释放

在这里插入图片描述
new [] 申请的要 delete [] 释放

定位new表达式(了解)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化

什么是内存池

我们很多程序都是直接找内存分配空间,但是我们现在专门在内存中,开辟了一块大的空间单独给某一个程序分配,这叫内存池。
好比一个山上有一个和尚,要下山打水,他觉得太麻烦了,于是每次打水时都多打点,在山上修了一个池子。

使用格式

new (place_address) type或者new (place_address) type(initializer-list)

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()
{
	// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没

	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A;  // 注意:如果A类的构造函数有参数时,此处需要传参
	p1->~A();
	free(p1);
	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);
	p2->~A();
	operator delete(p2);
	return 0;
}

内存泄漏

什么时内存泄漏

内存泄漏是已经没有用的资源忘记释放了
比如

int main()
{
	int* a = new int[100 * 1024 * 1024];
	int* b = new int[100 * 1024 * 1024];
	int* c = new int[100 * 1024 * 1024];
	return 0;
}

内存泄漏的危害

我们试着运行上面这个程序
在这里插入图片描述
但是当我们结束程序的时候内存又恢复正常了
在这里插入图片描述

这意味着我们可以不用管内存泄漏吗?不是的因为我们写的很多程序都要跑在服务器上可不敢随时停止运行,这会造成巨大的经济损失,比如去年的滴滴服务器挂了一会儿亏了好几个亿。
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

谢谢观看

thank you

  • 14
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值