C++内存管理

C++的内存分区

C++和C语言的内存分区基本类似:
大致可以分成以下几个区,学习内存分区可以帮助我们更好的使用空间。

1.栈区:栈区是C++中经常要使用到的区域,函数的形参,局部变量等等都存储在栈上。
栈的特点:容量较小,大致有8M左右
生命周期:函数栈帧结束,栈里的局部变量和函数形参便会被系统自动回收

2.堆区:堆区是一种需要人为开辟的空间,可以通过 mallocnew等方式进行开辟。
堆的特点:容量很大,2G左右,编译器一般不会自动开辟堆上的空间,因此常常需要人为开辟。
生命周期:堆申请的数据伴随整个程序直到结束才被系统回收,因此很多时候为了防止内存泄露,我们都需要人为进行空间释放。

3.全局区:全局区存储的是在全局定义的变量。
全局区的特点:全局区定义的变量在全局都可以进行使用,因此需要注意不能在全局进行重复定义一个同名变量。
生命周期: 程序编译阶段即分配内存空间,直至程序运行结束被系统回收。

4.静态区:静态区存储的一般是static关键字修饰的变量。
静态区的特点:静态变量同时又分为全局静态变量和局部静态变量,这里需要进行区分,static修饰以后的变量,只有生命周期得到了改变,其作用域没有发生变化:
如静态局部变量:
i是定义在func函数内部的静态局部变量,其作用范围仍是func函数内部,其他函数无法进行使用,但是其不会随函数栈帧销毁而被系统回收,生命周期得到了延长。
在这里插入图片描述
生命周期:静态区的数据也是在程序编译期间就分配内存,直至程序运行结束被系统回收。

ps:(全局区和静态区其实是同一个分区,但是为了易于理解这里进行分开说明)

5.代码区:存放程序中一一条条代码被编译过后的指令,程序的入口地址,程序的名字。
特点:内存由系统控制,程序员不可随意写

6.常量区:存放字符串这类只能读不能修改的数据。

在这里插入图片描述
------------------------------------------------------------我是分割线💥💥💥💥💥💥💥💥💥💥------------------------------------------------------

内存分区面试题
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";
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. 选择题:
  2. 选择题:
    选项: A.栈 B.堆 C.静态区(全局区) D.常量区
    globalVar在哪里?____ staticGlobalVar在哪里?____
    staticVar在哪里?____ localVar在哪里?____
    num1 在哪里?____
    char2在哪里?____ *char2在哪里?___
    pChar3在哪里?____ *pChar3在哪里?____
    ptr1在哪里?____ *ptr1在哪里?____

------------------------------------------------------------我是分割线💥💥💥💥💥💥💥💥💥💥-----------------------------------------------------------

------------------------------------------------------------我是分割线💥💥💥💥💥💥💥💥💥💥-----------------------------------------------------------

------------------------------------------------------------我是分割线💥💥💥💥💥💥💥💥💥💥-----------------------------------------------------------

选项: A.栈 B.堆 C.静态区(全局区) D.常量区
(1)globalVar在哪里?  全局区 (2) staticGlobalVar在哪里?静态区
(3)staticVar在哪里?  静态区      (4) localVar在哪里?
(5)num1 在哪里 ?   
(6)char2在哪里?         (7) *char2在哪里?
(8)pChar3在哪里          (9)*pChar3在哪里?常量区
(10)ptr1在哪里                  (11)*ptr1在哪里?

   ( 1 ) (1) (1):globalVars:全局变量,存储在全局区
   ( 2 ) (2) (2):staticGlobalVar:静态全局变量,存储在静态区
   ( 3 ) (3) (3):staticVar:静态局部变量,存储在静态区
   ( 4 ) (4) (4):localVar:局部变量,存储在栈上
   ( 5 ) (5) (5):num1:局部变量,同样是在栈上
前五个对大家来说应该都是小菜一碟,接下来的几个就有同学可能会没注意然后被坑了。
   ( 6 ) (6) (6):char2是一个局部定义的数组,其存储在栈上
   ( 7 ) (7) (7):这里我们需要知道,常量字符串都是存储在常量区,而数组char存储的是常量字符串。编译器会为char2开好空间,然后将常量区的字符串拷贝到该空间。而数组名是数组首元素地址,解引用以后得到的是数组首元素,该元素存储在栈上。
在这里插入图片描述

   ( 8 ) (8) (8):局部定义的指针,存储在栈上。
   ( 9 ) (9) (9):指针pchr3指向常量区的字符串,解引用以后等于该字符串。所以为常量区。
   ( 10 ) (10) (10):局部指针,存储在栈上。
   ( 11 ) (11) (11):ptr1指向了堆区的一块空间,解引用等于这块空间。

malloc和free

在c语言中,我们常通过malloc函数在堆上申请空间,

int *p=(int*)malloc(sizeof(int))

malloc函数的返回类型为void类型,因此指针在接收的时候需要将其强转成对应类型,并且malloc每次是申请N个字节的空间,我这里就申请了4个字节的空间。
malloc的空间通常通过free函数进行释放
free(p)
free的参数是需要释放的空间的地址。

------------------------------------------------------------我是分割线💥💥💥💥💥💥💥💥💥💥-----------------------------------------------------------

new和delete

在 C++中,引入了另一种申请空间和释放空间的方式。
分别是new操作符和delete操作符

   ( 1 ) (1) (1)new操作符:
new操作符的作用是在堆上申请一块空间,其返回类型是所申请的数据类型。
1.new申请单个空间
在这里插入图片描述
2.new申请数组空间
在这里插入图片描述
   ( 2 ) (2) (2)delete操作符:
delete需要根据new进行选择使用,
分为两种情况:
1.new出来的空间不是数组
delete+空间地址

2.new出来的空间是数组
delete+[] +空间地址

------------------------------------------------------------我是分割线💥💥💥💥💥💥💥💥💥💥-----------------------------------------------------------

为何要引入new和delete

我们知道C++是一种兼容C语言的语言,既然已经有了malloc和free,为何又要引入new和delete。
我们下面进行下对比:
newPK malloc
   ( 1 ) (1) (1)new书写起来更加简洁

int *p =new int;
int *pp=(int*) malloc(sizeof(int));

new的书写的相比malloc更加简洁,不用特地进行强转返回类型。
但是这不是引入new的主要原因
   ( 1 ) (1) (1)newmalloc对于内置数据类型的初始化:
对于内置数据类型,new和malloc都不会对其进行初始化,下图中的a和b都是随机值。
在这里插入图片描述
   ( 3 ) (3) (3)new和malloc对于自定义类型的初始化
原因也正是出在了这里。

class person
{
public:
	person() {
		m_a = 10;
	}
	int m_a;
};


int main()
{
	person *p1 = (person*)malloc(sizeof(p1));
	person *p2 = new person;
}

这里分别使用newmalloc申请一个类对象的空间,通过调试查看两个对象的成员变量。
在这里插入图片描述
可以看出,通过malloc申请的对象p1,其成员变量m_a依旧没有被初始化,而new出来的对象p2的成员变量m_a被初始化为了10

我们可以思考一下,为何new对成员变量进行了初始化,而malloc没有对变量进行初始化?
可以说C++真的太爱构造函数了,new和malloc在申请类对象空间时候的区别就是:new出来的对象会调用其构造函数,而malloc不会。

   ( 4 ) (4) (4)freedelete对于自定义类型空间的释放
freedelete对于自定义类型空间的释放,基本没有什么区别,这里就不细说了。
关键同样还是在freedelete对于自定义类型空间的释放
delete释放自定义类型空间的时候会调用其析构函数,而free不会。
在这里插入图片描述
这里有同学可能会想:如果没有定义析构函数,那么delete和free应该也就没有本质区别了吧?
其实…还是有区别的…
有这样一种情况:一个类的成员变量为另一个的对象。
如student类的对象作为person类的成员:

class student{
public:
	~student()
	{
		cout << "student类的析构函数" << endl;
	}
};

class person
{
public:
	student s1;
};


int main()
{
	person *b = new person;
	delete b;
}

在这里插入图片描述
运行结果如下,person类没有书写析构函数,但是其析构函数会调用其成员变量student类的成员函数。

总结:关于 new 和delete
new先申请空间,接着调用构造函数进行初始化。
delete需要先调用析构函数进行资源清理,后释放空间

C++11新用法

在C++11中,new可以对申请的空间进行初始化,依旧是两种方式:
单个元素的初始化:

int *a=new int(5);

这块申请的空间就会被初始化为5
2.数组的初始化

int *p=new int[5]{1,2,3};

这里{}和普通数组的初始化一样,如果没写全,那剩下位置则被默认初始化为0。

new的底层原理

ps:因为对于自定义类型,newmallocdeletefree的区别不大。C++是面向对象的语言,我们着重了解对象。
通过调试模式进行查看new的代码:
我们发现,new汇编代码call了一句指令,这个指令是一个operator new()函数的跳转地址。
在这里插入图片描述
接着我们进行查看operator new()在C++中的源码:

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void *p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}

通过查看源码,可以看出,operator new()的底层原理其实是通过封装malloc进行实现的,我们知道在C语言中malloc函数申请空间失败会返回空。但是在C++中对于异常使用了一种抛异常的处理方式。如果malloc申请空间失败,C++则就会抛出该异常。
对于这个operator new函数,我们也可也对其进行使用:
其语法和malloc类似
返回值为void*
在这里插入图片描述
但是,其不会调用构造函数。
但是我们知道new会先开空间,然后调用构造函数。
因此我们可以认为new由这两部分构成:
在这里插入图片描述
既然有operator new(),那么对应就会有operator delete的出现。

new []的底层原理

在一些场景下,可能会有这种场景,我们需要创建多个对象,这时对应需要向堆区申请空间,就可以使用operator new[]()函数。
当通过new申请多个对象的空间的时候,就会调用operator new[]()函数,申请对应的空间,再执行多次构造函数。
关于operator new[]()函数其实是operator new()函数的封装。

class person {
public:
	int m_a;
};


int main()
{
	person *p = (person*)operator new[](sizeof person * 10);
}
delete的底层原理

operator delete的用法和free类似,
operator delete(),其参数为所要释放空间的地址。
并且delete的底层其实也是由free封装而成的。
operator new()一样,operator delete()只会释放空间,但是不会调用析构函数。
因此delete可由这两个部分组成:
在这里插入图片描述

内存池

在C++中,有时需要频繁的向堆区申请内存,但是每次申请的空间又很少。这时就可能会影响到程序的运行速度。
在C++中为此引入了内存池这一概念,举例来说:在一座山上,没有水源,如果想要用水必须去山下的小河打水,平常要用水的场景很多,但是频繁的去山下打水既消耗时间又不省力,这时就可以在一次打很多水,然后灌入到山上的水井中。内存池扮演的角色就如同例子中的水井

为了搭配内存池的更好使用,上面我们讲的operator new()函数其实是可以进行函数重载的,这时就可以通过重载的operator new()函数在内存池里进行申请空间。

定位new

概念:定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
定位new的功能其实也是和operator new()函数进行搭配使用,我们知道operator new()函数只会申请空间,但是不是调用构造函数,这时就可以通过使用定位new进行调用构造函数。这里大家可能会疑惑为什么不能直接用new,其实这种搭配方式有时候也是为了内存池之类的场景进行使用。

格式:new(内存地址)类名;
并且定位new也可以调用有参构造
new(内存地址)类名(参数);
在这里插入图片描述

空间的申请和释放

在使用malloc或者new申请空间的时候,可以这样理解,操作系统会在空间中多申请几个作为标志位,记录本轮申请了几个空间,这样一来,在使用delete或者free释放空间的时候,OS只需要查看标记位就可以知道本轮需要释放的空间的大小

内存泄露

什么是内存泄露:动态申请的内存,不使用了,但是没有进行释放
ps:如果一个内存泄露的进程正常结束,其一般不会有太多的危害,因为在进程结束后,操作系统会对其空间进行回收。
内存泄露的危害主要还是因为进程的非正常结束,如僵尸进程,这类进程的特点就是会不断的运行,同时不断消耗服务器的内存空间,导致服务器运行速度越来越慢,甚至宕机。

内存泄露的解决方法:
1.智能指针。
2.一些检查内存泄露的工具。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凤梨罐头@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值