文章目录
1 内存分区模型
内存分区的意义:不同区域存放的数据具有不同的生命周期,增加编程的灵活性。
C++内存分区模型包含4个区:
(1)代码区:存储函数体的二进制代码及CPU执行的机器指令;由操作系统进行管理。
(2)全局区:存储全局变量、静态变量和常量(全局常量、字符串常量);在程序结束后,由操作系统管理并释放。
(3)栈区:存储函数参数值(形参值)、函数返回值、局部变量和局部常量等;由编译器自动分配和释放。
(4)堆区:由程序员自行分配和释放;若未主动释放,会导致内存泄露,在程序结束后,由操作系统管理并释放。
2 程序运行前:代码区 & 全局区
程序编译生成可执行程序(.exe文件),可执行程序在未执行前即存在代码区和全局区等2个区域。
2.1 代码区
(1)代码区存储CPU执行的机器指令;
(2)代码区是共享的:对于频繁被执行的程序,内存中只需存储一份代码,避免造成内存资源浪费;
(3)代码区是只读的:防止程序意外地修改已存储的指令。
2.2 全局区
全局区存储全局变量、静态变量(static
修饰的变量)、字符串常量和全局常量(const
修饰的全局变量)。全局区的数据在程序结束后,由操作系统管理并释放。
(1)data区
:主要存储已初始化的全局变量、静态变量和常量。
(2)bss区
:主要存储未初始化的全局变量和静态变量。
(3)常量区
:存储字符串常量和全局常量(const
修饰的全局变量)等常量;全局区中的较小区域。
注:bss区中未初始化的数据,在程序执行前自动被系统初始化为
0
或NULL
。
示例:
#include <iostream>
using namespace std;
/* 全局变量 */
int g_var = 1;
/* 全局常量:const修饰的全局变量 */
const int c_g_var = 5;
int main() {
/* 局部变量 */
int l_var = 2;
/* 静态变量:static修饰的变量 */
static int s_var = 3;
/* 常量 */
//字符串常量:"hello"
/* 其他常量:const修饰的变量 */
//const修饰的局部变量:局部常量
const int c_l_var = 4;
cout << "全局变量g_var:" << &g_var << endl; //0066C000 全局区
cout << "静态变量s_var:" << &s_var << endl; //0066C004 全局区
cout << "常量-字符串常量:" << &"hello" << endl; //00669B74 全局区
cout << "常量-全局常量c_g_var:" << &c_g_var << endl; //00669B30 全局区
cout << "局部变量l_var :" << &l_var << endl; //00EFFB34 非全局区
cout << "局部常量c_l_var:" << &c_l_var << endl; //00EFFB28 非全局区
return 0;
}
小结:
C++在程序运行前分为全局区和代码区;
全局区存储:全局变量、静态变量(static
修饰的变量)、常量(①字符串常量;②全局常量,即const
修饰的全局变量)。
注:局部变量和局部常量(const
修饰的局部变量)在栈区。
3 程序运行后:栈区 & 堆区
3.1 栈区
栈区数据由编译器自动分配和释放,存储函数参数值(形参值)、函数返回值、局部变量和局部常量等。
注:栈区的数据在执行完毕后由编译器自动释放,不能返回局部变量的地址(即不能使用指针访问和操作该内存)。但编译器会保留一次局部变量的地址(可临时访问一次),随后该内存被释放,无法再次访问。
示例:
#include <iostream>
using namespace std;
int* func(int param) { //函数形参值-栈区
param = 10;
int a = 5; //局部变量-栈区
/* 不要返回局部变量的地址 */
return &a;
}
int main() {
int* p = func(0);
//编译器会保留一次局部变量的地址(可临时访问一次),随后该内存被释放,无法再次访问
cout << *p << endl; //5
cout << *p << endl; //12062780(乱码) //内存已被释放
return 0;
}
3.2 堆区
堆区数据由程序员自行分配和释放。若未主动释放,当整个程序全部结束时由操作系统回收。
注1:堆区主要用于动态内存分配,在内存中介于全局区的bss区和栈区之间。
注2:C++中,使用关键字new
在堆区开辟内存空间。
注3:堆区数据在内存未释放前可使用指针多次访问和操作该内存,直至主动释放内存或程序执行完毕后由操作系统释放内存。
示例:
#include <iostream>
using namespace std;
//使用关键字new在堆区开辟内存空间
int* func() {
int* p = new int(5);
return p;
}
int main() {
int* p = func();
//堆区数据在内存未释放前可使用指针多次访问或操作,直至主动释放内存或程序执行完毕后由操作系统释放内存
cout << *p << endl; //5
cout << *p << endl; //5
*p = 10;
cout << *p << endl; //10
cout << *p << endl; //10
return 0;
}
4 new操作符 & delete操作符
C++中,使用new操作符
开辟堆区数据,delete操作符
释放堆区内存。
注:堆区内存空间由程序员自行分配和释放。若未主动释放,当整个程序全部结束时由操作系统回收。
4.1 堆区变量
开辟内存:数据类型 *指针名 = new 数据类型(初始值);
释放内存:delete 指针名;
注1:使用
new操作符
创建堆区变量,返回堆区内存的地址,可使用相同数据类型的栈区指针变量接收。
注2:释放内存后,再次访问该内存空间为非法操作,编译器报错:读取访问权限冲突;使用未初始化的内存。
示例1: 堆区变量的创建与释放
#include <iostream>
using namespace std;
//使用关键字new在堆区开辟内存空间
int* func_var() {
int* p = new int(5); //开辟堆区内存
return p;
}
int main() {
int* p = func_var();
cout << *p << endl; //5
/* 无法访问已释放的内存 */
delete p; //释放内存
//cout << *p << endl; //异常:读取访问权限冲突。使用未初始化的内存"p"
return 0;
}
4.2 堆区数组
开辟内存:数据类型 *指针名 = new 数据类型[数组长度];
释放内存:delete[] 指针名;
注:使用
new操作符
创建堆区数组,返回堆区内存的首地址,可使用相同数据类型的栈区指针变量接收。
示例2:堆区数组的创建与释放
//堆区开辟数组
#include <iostream>
using namespace std;
//使用关键字new在堆区开辟数组
int* func_arr() {
int* pArr = new int[5]; //开辟堆区数组
return pArr;
}
int main() {
int* arr = func_arr();
//为数组元素赋值
for (int i = 0; i < 5; i++) {
arr[i] = i; //使用索引操作数组元素
}
//遍历数组元素
for (int i = 0; i < 5; i++) {
cout << arr[i] << endl; //使用索引访问数组元素
}
/* 无法访问已释放的内存 */
delete[] arr; //释放内存
//cout << *arr << endl; //异常:读取访问权限冲突。
return 0;
}