C++内存管理

前言

1.内存分布

在这里插入图片描述
接下来看一段代码,分别区分这些东西在内存的哪个区

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);
}
  1. 选择题:
    选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
    globalVar在哪里?C 全局变量,静态区
    staticGlobalVar在哪里?__C__静态变量,静态区
    staticVar在哪里?C
    localVar在哪里?__A__函数里面正常的变量,那就在栈区
    num1 在哪里?___A_函数里面正常的数组,那就在栈区
    char2在哪里?A 函数里面正常的数组,那就在栈区,这个实际上是在栈上创建了一个数组,数组里面的值就是这些
    在这里插入图片描述

    char2在哪里?___所以char2指的是a也在栈上,char2这个数组名的存储也在栈上,指向abcd
    pChar3在哪里?____ pchar3是函数里面的变量,所以在栈上 ,指向静态区的字符串
    在这里插入图片描述
    pChar3在哪里?____而“abcd”这是一个静态的字符串,不可修改的,所以pchar3就指向静态区的a,所以前面还要加上一个const,不然权限就缩小了
    ptr1在哪里?____ 栈
    *ptr1在哪里?____堆

2.C++内存管理方式

C语言中动态内存管理方式:malloc/calloc/realloc/free
而在C++中就是new 和 delete了
这两个不是函数,是操作符
我们先介绍这两个东西的用法

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

int* p1 = new int;这句话表示开辟一个int大小的空间,p1来指向
delete p1;这句话就表示释放开辟的那段空间了

int main()
{
	int* p1 = new int(1);
	cout << (*p1) << endl;
	delete p1;
	return 0;
}

在这里插入图片描述
开辟的时候还可以初始化

int main()
{
	int* p1 = new int[10];
	delete[] p1;
	return 0;
}

这个就表示开辟10个int大小的空间,然后对应释放空间要用delete[]
这个就表示为数组了

int main()
{
	int* p1 = new int[10] {1,2,3,4,5};
	for (int i = 0; i < 10; i++)
	{
		cout << p1[i] << ' ';
	}
	delete[] p1;
	return 0;
}

在这里插入图片描述

在创建的时候的后面加上{}就表示是对数组的初始化了,数组后面没写的默认为0

class A
{
public:
	A(int val = 1)
	{
		cout << "A(int val=1)" << endl;
	}
	A(int a, int b)
	{
		cout << "A(int a, int b)" << endl;
	}
private:
	int _val;
};

int main()
{
	A* p = new A;
	delete p;
	return 0;
}

在这里插入图片描述
相应的,自定义类型也可以用new来创建,而且创建的时候会调用构造函数,delete释放的时候会调用析构函数,new创建时,如果不传参就会调用默认构造函数

class A
{
public:
	A(int val = 1)
	{
		cout << "A(int val=1)" << endl;
	}
	A(int a, int b)
	{
		cout << "A(int a, int b)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _val;
};

int main()
{
	A* p = new A(1);
	A* p1 = new A[10]{ 1,2,3,4,{2,3} };
	delete p;
	delete[] p1;
	return 0;
}

在这里插入图片描述
对于数组的释放一定要用delete[],不然很容易出错
数组的赋值也是这样的,赋的什么值就对应去哪个构造函数,没有赋值的话就去调用默认函数
数组的每个赋值就相当于是这样的
p1[0]=1,p2[1]=2,而p1[0]又是一个自定义类型,所以这里相当于单参数的构造函数的隐式类型转换
{2,3}就相当于多参数的隐式类型转换
因为new和delete会自动调用构造函数和析构函数,而malloc和free就不行所以以后尽量用new和delelte

3.operator new与operator delete函数

其实在new操作符里会调用operator new这个函数,这是一个函数,不是运算符重载
operator delete也是一样的道理,delete操作符里会调用operator delete这个函数

int main()
{
	int* p = (int*)operator new(sizeof(int));
	*p = 1;
	cout << (*p) << endl;
	operator delete(p);
	return 0;
}

在这里插入图片描述
operator new的使用与malloc一模一样
operator delete的使用与free一模一样
因为operator delete是free的分装,就是在free的基础上加了一些东西
operator new也是malloc的分装,在原先malloc的基础上加了一些东西
加了什么东西呢,加的是抛异常,这是个什么东西呢,这个我先不讲,先留着,后面会说明的,反正这个东西就和以前C语言的错误码是比较类似的东西,是针对错误进行了说明,这个有什么用呢
以前用malloc开辟空间,每开辟一个都要做判断,看是否开辟失败,开辟n次,你就要判断n次,就要写这段代码n次,但是有了抛异常,你就只需要写这个代码一次就可以了,这就是它的作用
所以总结来说
new=operator new+构造函数=malloc+抛异常+构造函数
delete=operator delete+析构函数=free+抛异常+析构函数
而同样的
delete[]也是=operator delete[]+析构函数
而operator delete[]的底层就是delete[]
在这里插入图片描述

这就是为什么delete要调用析构函数和new要调用构造函数的原因,因为如果只有operator delete 的话,如果类里面的变量还要指向一段空间的话,那么是无法释放这段空间的,只有析构函数才可以

4.new和delete的实现原理

在讲这个之前我们先来看很多的代码和其结果

int main()
{
	int* p = new int;
	free(p);
	return 0;
}

这段代码没有问题

int main()
{
	int* p = new int[10];
	free(p);
	return 0;
}

int main()
{
	int* p = new int[10];
	delete p;
	return 0;
}

这段代码也没有问题
所以对于内置类型,不管是malloc还是new,不管是free(和operator delelte是一样的效果,所以我只说一个)还是delelte,随便你怎么混合使用,都不会有什么问题,甚至对于数组只使用delete,不使用delete[]都没有问题,所以内置类型随你怎么用,开辟有释放就可以了,不必new对应delete

class A
{
public:
	A(int val = 1)
	{
		cout << "A(int val=1)" << endl;
	}
	A(int a, int b)
	{
		cout << "A(int a, int b)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _val;
};
int main()
{
	A* p = new A;
	free(p);
	return 0;
}

在这里插入图片描述
对于自定义类型的单个对象可以这样混着用,没问题

class A
{
public:
	A(int val = 1)
	{
		cout << "A(int val=1)" << endl;
	}
	A(int a, int b)
	{
		cout << "A(int a, int b)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _val;
};
int main()
{
	A* p = new A[10];
	free(p);
	return 0;
}

在这里插入图片描述
在这里也是终于出问题了,对于自定义类型的数组,单单只对它free程序会崩溃的

int main()
{
	A* p = new A[10];
	delete p;
	return 0;
}

当然如果改成delete,改成operator delete还是一样的结果
只有适合它的delelte[]是没有问题的,这到底是为什么呢
在这里插入图片描述
在这里插入图片描述
我们把开辟时的代码转到反汇编看看,还发现了这里实际开辟了44个字节空间,为什么呢,
好了,现在开始解答疑惑
在这里插入图片描述
对于自定义类型的数组,编译器会在前面多开辟一个int类型的空间,因为这是为了记录后面要析构多少次,不然只释放不析构还是不行的,因为要先析构,所以要有个数据来记录析构多少次,然后再释放,开辟的时候是这样的,开辟44字节,然后p还指向了中间点的位置,不是最开始的位置,在delete[]开始释放的时候,回先去访问前面的10,再从10开始释放就可以了
所以说这就是为什么只用free只用delete释放是不可以的了,是会崩溃的,因为他俩只会从p指向位置开始释放,不会向前走一步在释放,而从开辟空间的非开头位置开始释放就是会崩溃,所以就是这个原因

class A
{
public:
	A(int val = 1)
	{
		cout << "A(int val=1)" << endl;
	}
	A(int a, int b)
	{
		cout << "A(int a, int b)" << endl;
	}
	//~A()
	//{
	//	cout << "~A()" << endl;
	//}
private:
	int _val;
};
int main()
{
	A* p = new A[10];
	delete p;
	return 0;
}

在这里插入图片描述
但是如果这样的话,就不会崩溃了,为什么呢
那我先问为什么会在前面开辟空间来记录呢?还不是因为,你自己手动实现了析构函数,这就说明了析构很重要,析构是必须得,不析构可能就会内存泄漏
如果没有显示实现析构函数,就说明了析构不重要,不析构也没事,不析构也不会内存泄漏,所以就不析构了,所以就不单独开辟空间来记录了,所以就没事了

5.定位new表达式

接下来我们不用new,delete来实现一下new和delete的功能

class A
{
public:
	A(int val = 1)
	{
		cout << "A(int val=1)" << endl;
	}
	A(int a, int b)
	{
		cout << "A(int a, int b)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _val;
};
int main()
{
	A* p = new A;
	p->~A();
	operator delete(p);
	return 0;
}
p->~A();
operator delete(p);
这个就是对delete的实现了

在这里插入图片描述
但是要实现new的话没那么简单,因为构造函数不能显示调用,只能在创建对象的时候用
所以这里就提供了其他的方法

int main()
{
	A* p = (A*)operator new (sizeof(A));
	new(p)A;
	p->~A();
	operator delete(p);
	return 0;
}

在这里插入图片描述
这个调用的是默认构造

int main()
{
	A* p = (A*)operator new (sizeof(A));
	new(p)A(10);
	p->~A();
	operator delete(p);
	return 0;
}
int main()
{
	A* p = (A*)operator new (sizeof(A));
	new(p)A(10,2);
	p->~A();
	operator delete(p);
	return 0;
}

这两个都是构造函数,所以这就实现了new
但是这样有什么用呢,直接使用new不好吗,这里的用处就是在内存池的时候有用
在这里插入图片描述
所谓内存池就是在栈中拿来一部分空间,然后这样分开使用,就可以调用内存池的空间,就不调用栈的空间了,因为new默认调用的空间是栈中的,所以要访问内存池就要用上面这个方法了,具体什么方法,还有内存池怎么实现,现在都不讲
内存池的功能主要是提高效率的,这就相当于家长给了你生活费你自己用,没有内存池就相当于每次花钱都要找家长要,你没有生活费,那这样效率就很低

6. 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在释放空间前会调用析构函数完成空间中资源的清理

总结

下一篇博客我们来学习一些模版

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值