1 内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义:
不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程
1.1 程序运行前
在程序编译后,生成了exe
可执行程序,未执行该程序前分为两个区域
i.代码区
作用: 存放CPU
执行的机器指令
特点:
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
- 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
ii.全局区
作用: 存放全局变量和静态变量以及常量 (包含字符串常量
和const修饰的全局变量
)
特点: 该区域的数据在程序结束后由操作系统释放
示例:
#include<iostream>
using namespace std;
//定义两个全局变量g_a,g_b
int g_a = 10;
int g_b = 10;
//定义两个const修饰的全局变量
const int c_g_a = 10;
const int c_g_b = 10;
//定义两个宏常量
#define d_g_a 10
#define d_g_b 10
int main() {
//定义两个局部变量a,b
int a = 10;
int b = 10;
//定义两个静态变量s_a,s_b
//注意:局部变量被static修饰之后,属于静态变量
static int s_a = 10;
static int s_b = 10;
//定义两个const修饰的局部变量
const int c_l_a = 10;
const int c_l_b = 10;
//通过地址可以判断出局部变量、全局变量、静态变量以及常量()所在区域
cout << "局部变量a的地址为:" << &a << endl;
cout << "局部变量b的地址为:" << &a << endl;
cout << endl;
cout << "全局变量g_a的地址为:" << &g_a << endl;
cout << "全局变量g_b的地址为:" << &g_b << endl;
cout << endl;
cout << "静态变量s_a的地址为:" << &s_a << endl;
cout << "静态变量s_b的地址为:" << &s_b << endl;
cout << endl;
//常量目前接触到的有三种,字符串常量和const修饰的变量以及宏常量
//字符串常量
cout << "字符串常量的地址为:" << &"hello world" << endl;
cout << "字符串常量的地址为:" << &"10" << endl;
cout << endl;
//const修饰的变量分为:const修饰的全局变量和const修饰的局部变量
//1.const修饰的全局变量
cout << "const修饰的全局变量c_g_a的地址为:" << &c_g_a << endl;
cout << "const修饰的全局变量c_g_b的地址为:" << &c_g_b << endl;
cout << endl;
//2.const修饰的局部变量
cout << "const修饰的全局变量c_l_a的地址为:" << &c_l_a << endl;
cout << "const修饰的全局变量c_l_b的地址为:" << &c_l_b << endl;
cout << endl;
//3.宏常量
//注意:define的宏变量在使用时是替换不占内存,因此不可寻址
cout << "宏常量d_l_a的地址为:" << d_g_a << endl;
cout << "宏常量d_l_b的地址为:" << d_g_b << endl;
system("pause");
return 0;
}
打印结果:
总结:
- C++中在程序运行前分为全局区和代码区
- 代码区特点是共享和只读
- 全局区中存放全局变量、静态变量、常量
- 常量区中存放 const修饰的全局常量 和 字符串常量
1.2 程序运行后
iii.栈区
作用: 存放局部变量以及函数中定义的形参变量等,但栈区存储数据空间由编译器管理开辟和自动释放
注意事项: 不要返回局部变量的地址,栈区存储数据空间由编译器管理开辟和自动释放
示例:
#include<iostream>
using namespace std;
//注意:形参变量b和函数中定义的局部变量a中的值都存放在栈区,当函数执行完之后,存放在栈区的数据由编译器自动释放
//定义一个函数function1,返回形参变量的地址
int* function1(int b) {
return &b;//返回形参变量的地址
}
//定义一个函数function1,返回局部变量的地址
int* function2(int b) {
int a = b;
return &a;//返回形参变量的地址
}
int main() {
//调用指针函数function1,并接收返回的形参变量的地址
int * p1 = function1(10);
//注意:指针函数function1中的形参变量b中的值在函数调用完之后就自动释放了,导致返回形参的地址指向的值为未知,所以解引用得不到值
cout << "指针p1的地址为:" << p1 << "\t" << "指针p1中的值为:" << *p1 << endl;
cout << "指针p1的地址为:" << p1 << "\t" << "指针p1中的值为:" << *p1 << endl;
//调用指针函数function2,并接收返回的形参变量的地址
int* p2 = function2(10);
//注意:函数function2中定义的局部变量a中的值在在函数调用完之后就自动释放了,导致返回局部变量a的地址指向的值为未知,但是编译器会做一次值保留,当再次进行输出时,值不在保留
cout << "指针p2的地址为:" << p2 << "\t" << "指针p2中的值为:" << *p2 << endl;
cout << "指针p2的地址为:" << p2 << "\t" << "指针p2中的值为:" << *p2 << endl;
system("pause");
return 0;
}
打印结果:
iv.堆区
作用: 主要利用new在堆区开辟内存
特点: 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
示例:
#include<iostream>
using namespace std;
//定义一个function函数返回指针指向的地址
int* function() {
//利用new关键字,可以在堆区内开辟一个存储数据的空间,同时将指向堆区的地址保存在指针中,但指针本质也是局部变量,存放在栈区中,其指向的值在堆区中
int* p= new int(10);
return p;
}
int main() {
//调用function函数时,在堆区中开辟空间用来保存数据,并将数据在堆区的地址返回,这个地址已经指向堆区中存储的值,所以在函数function被调用完之后,不会因为栈区的自动释放而影响堆区通过这个地址解引用获取值
int* point = function();
cout << "数据在堆区的地址为:" << point << "数据在堆区的值为" << *point << endl;
cout << "数据在堆区的地址为:" << point << "数据在堆区的值为" << *point << endl;
cout << "数据在堆区的地址为:" << point << "数据在堆区的值为" << *point << endl;
system("pause");
return 0;
}
打印结果:
总结:
- 堆区数据利用new关键字进行开辟内存
- 堆区数据由程序员管理开辟和释放
1.3 new操作符
作用: 由程序员手动利用new操作符在堆区开辟存储数据空间,手动释放堆区数据利用操作符 delete,若程序员不释放,程序结束时由操作系统回收
语法规则:
new 数据类型;
利用new创建的数据,会返回该数据对应的类型的指针
示例1: 基本语法
#include<iostream>
using namespace std;
//定义一个function函数返回指针指向的地址
int* function() {
//利用new关键字,可以在堆区内开辟一个存储数据的空间,同时将指向堆区的地址保存在指针中,但指针本质也是局部变量,存放在栈区中,其指向的值在堆区中
int* p= new int(10);
return p;
}
int main() {
//调用function函数时,在堆区中开辟空间用来保存数据,并将数据在堆区的地址返回
int* point = function();
cout << "数据在堆区的地址为:" << point << "数据在堆区的值为" << *point << endl;
cout << "数据在堆区的地址为:" << point << "数据在堆区的值为" << *point << endl;
cout << "数据在堆区的地址为:" << point << "数据在堆区的值为" << *point << endl;
//在堆区中存储的数据,由程序员管理开辟和释放,对于开辟使用new关键字,对于释放使用delete关键字
delete point;
cout << "数据在堆区的地址为:" << point << "数据在堆区的值为" << *point << endl;//堆区中存储的数据已经被释放,再次访问就变成非法操作,直接报错!!!
system("pause");
return 0;
}
效果如图:
示例2:开辟数组
#include<iostream>
using namespace std;
#define max_arrys 10
//创建一个function3函数返回一个指针类型
int * function3() {
//在堆区创建一个整型数据的数组,并设置了开辟的空间存储数据容量为10
int* arrys = new int[max_arrys];
//添加随机种子,作用是利用当前系统时间生成随机数,防止每次随机都是一样,如果报错,注意:头文件引入 #include <ctime>
srand((unsigned int)time(NULL));
//对数组进行赋值
for (int i = 0; i < max_arrys;i++) {
arrys[i] = i + rand()%90;
}
return arrys;
}
int main() {
//调用function3函数,在堆区中开辟空间用来保存数组数据,并将数组在堆区的地址返回
int* p = function3();
//将数组中的数据输出
cout << "数组arrys:[";
for (int i = 0; i < max_arrys; i++)
{
if(max_arrys - 1 > i) {
cout << p[i] << ", ";
}
else
{
cout << p[i] << "]" << endl;
}
}
//在堆区中存储的数组数据,由程序员管理开辟和释放,对于开辟使用new关键字,对于释放使用delete关键字
//注意:释放堆区中的数组,必须在delete后面加[]
delete[] p;
system("pause");
return 0;
}
读万卷书,行万里路,博主会持续更新学习笔记,如有问题,可以留言和私信,我们一起进步!!!