C++学习之路—内存空间的布局

简介

C++工程下的内存大致分为四个部分:栈空间、堆空间、代码区、全局区

栈空间

每调用一个函数就会给它分配一段连续的栈空间(主要用于存放函数内部的局部变量)。函数调用完成之后,这段栈空间就会被自动回收。这也就是为什么说:局部变量的生命周期是函数调用期间。

void func()
{
	int a = 10;//内存在栈空间,调用完之后,内存自动回收
	double b = 10.0;//内存在栈空间,调用完之后,内存自动回收
	const char* str = "xuhongbo";//内存在栈空间,调用完之后,内存自动回收
}

int main()
{
	func();//调用函数时,栈空间分配一段连续的内存空间用于存放a,b,str变量,调用结束之后,就会自动释放,一点也不浪费
	getchar();
	return 0;
}

代码区

用于存放所有代码编译成机器指令的内存区,只要是你写的代码(无论是函数、变量、类等等),最终都会被编译成010101, 然后这些010101就会被存放在代码区。代码区是只读的,所以局部变量不能放进去。

全局区

用于存放定义在函数外部的全局变量,整个程序运行过程中都会存在,不会被回收,其生命周期是整个程序运行时间。

堆空间

需要主动申请内存区,并且在变量或者函数结束后,需要手动释放

栈空间、全局区都能存放东西,那堆空间存在的价值是什么????

在程序运行的过程中,为了能够自由的控制内存的生命周期、大小,会经常使用堆空间申请内存
说白了就是,我用多少我就申请多少,我想什么时候用就什么时候用。不像全局变量,必须贯穿
整个程序运行时期,万一我只用了一次,就很浪费。

堆空间其实价值很高,比如植物大战僵尸里的僵尸,僵尸的死亡是需要条件的,如果满足了一定条件,我就释放僵尸的内存,僵尸就死了,这样才是最高效的编程。而不是放在全局区或者放在栈空间

申请\释放堆空间

1、malooc\free方法

  • 第一种方法:malooc\free函数可以完成(C语言和C++都可以用)
int main()
{
	//申请\释放堆空间
	//malooc\free函数可以完成
	//申请四个字节的堆空间内存,存放int型的变量,返回的指针p是这段内存的首地址
	int *p_int = (int *)malloc(4);
	//malloc函数是没有返回值的,为void*,所以想要返回不同类型的指针,需要强制转换一下,
	//比如(int *)malloc(size_t)就可以返回int*指针
	*p_int = 10;//把10放在堆空间里,因为10是int类型,正好可以放满,	
	free(p_int);//释放在堆上开辟的内存空间
	getchar();
	return 0;
}

注意:申请堆空间成功后,返回的是那一块堆空间的地址,所以堆空间的返回值必须用指针接着,所以上述的*p_int是malloc的返回值

2、new\delete方法

  • 第二种方法:new\delete关键词也可以完成(C++独有,更加重要)
void new_func()
{
	//只要看到new,就是向堆空间申请内存
	int *p_int = new int;//看到new int 就知道申请了4个字节大小的一个int大小的堆空间内存,并让指针p_int指向这一块堆空间内存
	char *p_char = new char;//new char同理,就知道想堆空间申请了一个字节的char类型的内存,既然是char类型,必须是char类型的指针指向这块内存
	*p_int = 10; //为这4个字节存放一个int型的数值,正好填满
	*p_char = 'a';//为这1个字节存放一个char型的字符,正好填满

	delete p_int;//new完,用完,就可以释放了,用delete关键字
	delete p_char;
}

new[]\delete[]可以申请多个数据类型的内存,比如new int[2]->申请两个int类型的字节,也就是8个字节,可以存放两个int变量。注意,new[]多个内存的话,释放时必须delete[],‘[]’不可省略。

void new_func()
{
	//那如果我想要给char申请多个字节呢,比如4个,那代码就得这么写
	char *p_char_mutil = new char[4];//这样的话,申请了四个字节的内存,用来存放四个char变量
	p_char_mutil[0] = 'a';
	p_char_mutil[1] = 'b';
	p_char_mutil[2] = 'c';
	p_char_mutil[3] = 'd';
	//下面的很重要!!!new[]出来的内存,释放必须用delete[]
	delete p_char_mutil;//可能只释放其中一个字节
	delete[] p_char_mutil;//这才是正确写法!!!!!
}

3、申请\释放需要注意的地方

  • 申请和释放必须是一对一的关系,不然可能会存在内存泄漏。比如:new之后,本应该用delete释放,却用free释放,就会出现莫名其妙的错误。
  • 在堆空间申请多少字节的内存,在释放时必须全部收回,不可以出现这种情况(堆空间申请了四个字节,但在回收的时候只回收2个字节,或者只回收1个字节)。一次性申请多少,就一次性回收多少
  • 堆空间申请出来,你想用来存放什么类型的数据,是你自己的事。你可以拿来存放int,也可以是char,也可以是double。表现在程序中就是返回值的类型不同。

如果把堆空间的申请放在函数里,会随着函数的调用自动回收吗?

我们都知道,函数里的局部变量存放在栈空间,而栈空间的特点数会随着调用完毕之后自动回收局部变量的内存。那么,如果把堆空间的申请放在函数里,然后我不进行手动释放,那么这段内存会自动回收吗?

void malloc_func()
{
	//把堆空间的申请放在函数里,会随着函数的调用自动回收吗?
	int *p = (int *)malloc(4);//申请四个字节的堆空间内存,存放int型的变量,返回的指针p是这段内存的首地址
	*p = 10;//把10放在堆空间里,因为10是int类型,正好可以放满

	//函数调用完毕,栈空间的*p变量没了,但申请的四个字节的堆空间内存还在,啥时候碰到free,堆空间才回收
	//即使执行上述两行代码,这四个字节也是不会回收的,那我就想让它回收,怎么办?---添加下面一行代码就可以做到
	free(p);//把指向堆空间首地址的指针free掉,那么这段堆空间也就会被回收了
}

答:不会被回收,函数调用完毕后,栈空间的上用于存放指针p本身的内存被回收了,但是创建的
堆空间内存依然在堆空间,必须通过手动释放掉。

在函数内部进行堆空间的申请所涉及的内存区域问题

void malloc_func()
{
	int *p = (int *)malloc(4);
	*p = 10;
}

上述在malloc_func函数里的两行代码牵扯到两块内存,一个是堆空间申请了4个字节的内存,用来存放10这个int型值;另一块是栈空间,存放着指向堆空间的地址,类型为int*,也就是p存放在栈空间,却指向堆空间。栈空间的指针指向堆空间,函数结束时回收的是指针本身,并没有回收指针指向的堆空间

堆空间初始化方法

堆空间创建完之后,需要进行堆空间的初始化,不同的申请方式,初始化的方式也不同。

1、malloc堆空间初始化方法

如果只申请一个数据类型的内存,直接赋值0也是没问题的。但是如果申请多个数据类型的空间,挨个初始化就显得很麻烦,所以C++提供了一个函数叫做memset(堆空间指针,想要初始化的值,初始化的内存区域大小)

void malloc_init()
{
	int *p = (int *)malloc(4);//申请四个字节的堆空间内存,存放int型的变量,返回的指针p是这段内存的首地址
	*p = 0;//初始化堆空间内容为0,这样没问题

	//10个int类型的空间被创建,我还能挨个初始化为0吗?显然太麻烦
	int *p_int = (int *)malloc(sizeof(int) * 10);
	*p_int = 0;//初始化前4个字节的堆空间内容为0,那剩下9个int型字节,就没有办法初始化了,或者不能一行代码初始化了
	//memory set
	memset(p_int, 0, sizeof(int) * 10);//这句代码的作用是:从p_int这个地址开始,连续的sizeof(int) * 10=40个字节,全部初始化为0,连续的40个字节,都是0
	memset(p_int, 0, sizeof(int) * 5);//这句代码的作用是:从p_int这个地址开始,连续的sizeof(int) * 5=20个字节,全部初始化为0
	memset(p_int, 1, sizeof(int) * 5);//这句代码的作用是:从p_int这个地址开始,连续的sizeof(int) * 5=20个字节,全部初始化为1
}

顺便唠叨一下memset函数的作用

memset函数可不是只有初始化堆空间这一个作用。memset函数一个比较好的功能就是:可以比较快的将较大的数据结构(类实例化的对象,数组)内存清零

eg:Person person;//创建一个对象
			person.a = 1;
			person.b = 2;
			person.c = 3;
			person.d = 4;
			想要把a,b,c,d全部清零,如果不借助memset函数,就要一个一个赋值清零,但
			memset(&person,0,sizeof(person));一句代码就可以将类内的成员变量全部清零

2、new堆空间初始化方法

new方法初始化时,是没有函数的。申请时就顺便初始化了。在申请的数据类型后面加括号(),括号里的值是被初始化的值。

void new_init()
{
	//普通变量初始化
	int *p0 = new int;//没有初始化
	int *p1 = new int();//初始化为0,*p1指向的空间初始化为数字0
	int *p2 = new int(5);//初始化为5,*p2指向的空间初始化为数字5

	cout << *p0 << endl;//打印*p0指向的内容:-844137954,由于没有初始化,所以乱指
	cout << *p1 << endl;//打印*p0指向的内容:0
	cout << *p2 << endl;//打印*p0指向的内容:5

	//数据结构初始化,以数组为例:
	int *p3 = new int[3];          //数组里有三个int类型的值,都没有初始化
	int *p4 = new int[3]();        //数组里有三个int类型的值,初始化为0
	int *p5 = new int[3]{};        //数组里有三个int类型的值,初始化为0
	int *p6 = new int[3]{5};       //数组里有三个int类型的值,数组首元素初始化为5,剩下那俩被初始化为0!!!!!!!
	int *p7 = new int[3]{5,5,5};   //数组里有三个int类型的值,数组三个元素都初始化为5

}

new方法想要初始化,必须在括号里赋值。比如数组的话就是[](初始化值)。

类实例出来得对象的内存可以存在3种地方

类创建的对象的内存可以存在3种地方:对象被创建在哪,对象的成员变量就在哪,只有成员变量是放在对象所在的内存空间的,成员函数不在哦。

class Person
{
public:
	int m_a = 1;
	double m_b = 1.0;
	char m_c;
};

Person g_person;//g_person这个对象被创建在函数外,所以对象本身就是在全局区,同时对象下的所有成员变量都存放在全局区

void test()
{
	//f_person这个对象被创建在函数内,所以对象本身被创建在栈空间,同时对象下的所有成员变量都存放在栈空间
	Person f_person;
	
	Person *p_person = new Person();
	//p_person这个对象因为被创建在函数里,所以p_person指针对象在栈空间,但是对象下的所有成员变量都放在堆空间,这句代码涉及到两块内存,要注意
	//p_person指针对象存储着堆空间那块内存的地址,即指向堆空间。那堆空间有多大,取决于对象下的成员变量内存一共加起来有多大
}
  • 全局区(数据段):全局变量:Person g_person
  • 栈空间:函数里的局部变量(也包括主函数:void test()里的
  • 堆空间:动态申请内存(malloc、new等):Person *p_person = new Person();
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值