携手创造,一起成长!这是我参与「日新计划 · 8 月更文应战」的第2天,点击检查活动详情

内存分区模型

C++程序在履行时,将内存大方向划分为5个区域

运转前:

  • 代码区:寄存函数体的二进制代码,由操作体系进行办理的
  • 大局区(静态区):寄存大局变量和静态变量以及常量
  • 常量区:常量存储在这里,不允许修正

运转后:

  • 栈区:由编译器主动分配开释, 寄存函数的参数值,部分变量等
  • 堆区:由程序员分配和开释,若程序员不开释,程序完毕时由操作体系回收

内存四区意义:

不同区域寄存的数据,赋予不同的生命周期, 给咱们更大的灵敏编程

程序运转前

剖析

​ 在程序编译后,生成了exe可履行程序,未履行该程序前分为两个区域

代码区:

​ 寄存 CPU 履行的机器指令

​ 代码区是同享的,同享的目的是对于频频被履行的程序,只需要在内存中有一份代码即可

​ 代码区是只读的,使其只读的原因是避免程序意外地修正了它的指令

大局区:

大局变量静态变量寄存在此.

​ 大局区还包含了常量区, 字符串常量和其他常量也寄存在此.

​ ==该区域的数据在程序完毕后由操作体系开释==.

示例

原理:比照不同类型数据的地址区别区域划分。

//大局变量
int g_a = 10;
int g_b = 10;
//const润饰的大局变量:大局常量
const int c_g_a = 10;
const int c_g_b = 10;
int main() {
	//部分变量
	int a = 10;
	int b = 10;
	cout << "部分变量a地址为: " << (int)&a << endl;//(int)将地址信息转成10进制
	cout << "部分变量b地址为: " << (int)&b << endl;
	cout << "大局变量g_a地址为: " <<  (int)&g_a << endl;
	cout << "大局变量g_b地址为: " <<  (int)&g_b << endl;
	//静态变量
	static int s_a = 10;
	static int s_b = 10;
	cout << "静态变量s_a地址为: " << (int)&s_a << endl;
	cout << "静态变量s_b地址为: " << (int)&s_b << endl;
	//常量
    //1,字符串常量
	cout << "字符串常量地址为: " << (int)&"hello world" << endl;
	cout << "字符串常量地址为: " << (int)&"hello world1" << endl;
	//2.1const润饰的大局变量:大局常量
	cout << "大局常量c_g_a地址为: " << (int)&c_g_a << endl;
	cout << "大局常量c_g_b地址为: " << (int)&c_g_b << endl;
	//2.2const润饰的部分变量
	const int c_l_a = 10;
	const int c_l_b = 10;
	cout << "部分常量c_l_a地址为: " << (int)&c_l_a << endl;
	cout << "部分常量c_l_b地址为: " << (int)&c_l_b << endl;
	system("pause");
	return 0;
}

打印结果:

部分变量在一个段里;大局变量、静态变量、其他常量在另一个段里。

实例刨析:

部分变量:函数体内(栈区)
大局变量:函数体外
静态变量:函数体内(一般变量前加static)
常量:函数体内
1.字符串常量
2.const润饰的变量

​ (1)const润饰的大局变量:大局常量

​ (2)const润饰的部分变量不在大局区栈区

总结

  • C++中在程序运转前分为大局区和代码区
  • 代码区特色是同享和只读
  • 大局区中寄存大局变量、静态变量、常量
  • 大局区的常量区中寄存 const润饰的大局常量 和 字符串常量

易混点

区别静态变量(static)与const润饰的部分变量

程序运转后

栈区别析

栈区:

​ 由编译器主动分配开释, 寄存函数的参数值,部分变量等

示例

int * func()
{
	int a = 10;//部分变量寄存在栈区,栈区的数据在函数履行完成后主动开释。
	return &a;//回来部分变量的地址
}
int main() {
	int *p = func();
	cout << *p << endl;
	cout << *p << endl;
	system("pause");
	return 0;
}

易错点

不要回来部分变量的地址,栈区拓荒的数据由编译器主动开释,函数运转完毕后函数内的部分变量被开释,将无法使用传回的函数体内的部分变量的地址!

留意:依据编译器不同,编译器有时会保存,可是留意不要这么做!

图片刨析:假定编译器只会保存一次函数体内部分变量的地址,即传出的地址只能调用一次。

如果假定建立,那么*func()的调用将不受次数限制,由于func()每次传回的都是最新的地址,而*p只能调用一次,由于*p经过了部分变量的存储,编译器保存了一次地址后将地址开释之后p地址将失效,无法持续访问。

堆区别析

堆区:

​ 由程序员分配开释,若程序员不开释,程序完毕时由操作体系回收

​ 在C++中主要使用new在堆区拓荒内存

示例

int* func()
{
	int* a = new int(10);//使用new关键字将数据拓荒到堆区
	return a;//只针的本质是部分变量,放在栈上,指针保存的数据是放在堆区的
}
int main() {
	int *p = func();
	cout << *p << endl;
	cout << *p << endl;
	system("pause");
	return 0;
}

留意点

int(10)是编译器在栈区暂时虚拟出的一块空间,上图代码int* a表明并给这块内存起名为a,类比与4.2.2结构函数中的匿名目标:Person(10)单独写便是匿名目标(等同于int(10)存于栈上,加上new关键字就存在与堆区了。),特色:当前行完毕之后,立刻析构,即体系立即回收掉匿名目标。

结构函数相关代码比照:

//1、结构函数分类
// 依照参数分类分为 有参和无参结构   无参又称为默许结构函数
// 依照类型分类分为 一般结构和复制结构
class Person {
public:
	//无参(默许)结构函数
	Person() {
		cout << "无参结构函数!" << endl;
	}
	//有参结构函数
	Person(int a) {
		age = a;
		cout << "有参结构函数!" << endl;
	}
	//复制结构函数
	Person(const Person& p) {
		age = p.age;
		cout << "复制结构函数!" << endl;
	}
	//析构函数
	~Person() {
		cout << "析构函数!" << endl;
	}
public:
	int age;
};
//2、结构函数的调用
void test01() {
    //2.1  括号法(常用)
	Person p1;//调用无参结构函数,默许结构函数的调用
    Person p2(10);//有参结构函数
    Person p3(p2);//复制结构函数
	//留意1:调用无参结构函数不能加括号,如果加了编译器认为这是一个函数声明
	//Person p2()
	//2.2 显式法
	Person p2 = Person(10); //相当于给匿名目标Person(10)起个名字叫p2
	Person p3 = Person(p2);
	//Person(10)单独写便是匿名目标(等同于int(10)存于栈上),特色:当前行完毕之后,立刻析构,即体系立即回收掉匿名目标。
	//2.3 隐式转换法(简化的显现法)
	Person p4 = 10; // Person p4 = Person(10); 
	Person p5 = p4; // Person p5 = Person(p4); 
    //留意2:不能使用 复制结构函数 初始化匿名目标 编译器认为是目标声明
	//Person (p5);等同于Person p5;
}
int main() {
	test01();
	system("pause");
	return 0;
}

易错点

new int(10)回来的是地址,需要用指针接纳!

总结:

堆区数据由程序员办理拓荒和开释

堆区数据使用new关键字进行拓荒内存

new操作符

​ C++中使用==new==操作符在堆区拓荒数据

​ 堆区拓荒的数据,由程序员手动拓荒,手动开释,开释使用操作符 ==delete==

​ 语法: new 数据类型

​ 使用new创建的数据,会回来该数据对应的类型的指针

示例1: 基本语法

int* func()
{
	int* a = new int(10);
	return a;
}
int main() {
	int *p = func();
	cout << *p << endl;
	cout << *p << endl;
	//使用delete开释堆区数据
	delete p;
	//cout << *p << endl; //报错,开释的空间不可访问
	system("pause");
	return 0;
}

示例2:拓荒数组

//堆区拓荒数组
int main() {
	int* arr = new int[10];
	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;
	}
	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}
	//开释数组 delete 后加 []
	delete[] arr;
	system("pause");
	return 0;
}

易错点

开释数组要加中括号,如果不加中括号可能只会开释一个数据!

导图

扩展

而C言语的内存模型分为5个区:栈区、堆区、静态区、常量区、代码区。每个区存储的内容如下:

1、栈区:寄存函数的参数值、部分变量等,由编译器主动分配和开释,通常在函数履行完后就开释了,其操作方法类似于数据结构中的栈。栈内存分配运算内置于CPU的指令集,功率很高,可是分配的内存量有限,比如iOS中栈区的巨细是2M。

2、堆区:便是通过new、malloc、realloc分配的内存块,编译器不会担任它们的开释工作,需要用程序区开释。分配方法类似于数据结构中的链表。“内存泄漏”通常说的便是堆区。

3、静态区:大局变量和静态变量的存储是放在一块的,初始化的大局变量和静态变量在一块区域,未初始化的大局变量和未初始化的静态变量在相邻的另一块区域。程序完毕后,由体系开释。

4、常量区:常量存储在这里,不允许修正。

5、代码区:顾名思义,寄存代码