C/C++ 内存四区总结
更好的阅读体验:【前往作者个人站点 - AriesfunのBlog】
文章内容若有疑问或错误,欢迎交流、指正,互相学习哈。
0.简单概述
C/C++程序在执行时,将内存大方向划分为4个区域(内存四区)来存放所有数据。
程序运行前产生
- 代码区:存放函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量、静态变量以及常量
程序运行后产生
- 栈区:由编译器自动分配释放, 存放函数的参数值、局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义:不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程。
按是否在全局区来划分变量和常量:
虚拟地址空间(操作系统的视角)
虚拟地址空间被分配给进程,用于存储程序代码、数据和堆栈等。
小总结:
(1).全局区存放哪些数据?
全局变量、静态变量、常量(字符串常量,const修饰的全局常量或变量)
(2).不在全局区中的有哪些?
局部变量(栈区),const修饰的局部常量或变量
(3).区分const修饰的局部常量或变量:
const
修饰的局部常量或变量在声明时必须初始化,并且一旦初始化后,就不能再改变它们的值。
修饰常量:
const int MAX_VALUE = 100; // 这里用const声明并初始化了一个常量,其值不能被修改
修饰指针变量:
const int* ptr = &x; // ptr是一个指向常量的指针,指针内容不可修改
int* const ptr2 = &y; // ptr2是一个常量指针,指针本身地址不可修改
const int* const ptr3 = &z; // ptr3是一个指向常量的常量指针,指针本身及内容都不可修改
int main()
{
int x = 5;
const int* ptr = &x; // ptr 是一个指向常量的指针,指向的内容不能通过指针修改
// *ptr = 10; error
int y = 15;
int* const ptr2 = &y; // ptr2 是一个常量指针,指针本身不能被修改
*ptr2 = 20; // 指向的内容可以修改,可以修改y的值
// ptr2 = &x; error 常量指针本身地址不能被修改
const int z = 25;
const int* const ptr3 = &z; // ptr3 是一个指向常量的常量指针,既不能修改指针本身,也不能通过指针修改指向的内容
// *ptr3 = 30; error
// ptr3 = &x; error
}
1.程序运行前
在C++程序编译后,Windows环境下生成了.exe
可执行程序,未执行该程序前分为两个区域:代码区和全局区。
程序运行前总结:
- C++中在程序运行前分为全局区和代码区
- 代码区特点是共享和只读
- 全局区中存放全局变量、静态变量、常量
- 常量区中存放 const修饰的全局常量 和 字符串常量
01.代码区
特点:只读、共享
存放 CPU 执行的机器指令(即二进制0101)
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令;
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码可。
02.全局区
全局变量和静态变量,还包含常量区, 字符串常量和其他常量也存放在此。
该区域的数据在程序结束后由操作系统释放。
代码示例:
#include
using namespace std;
// 全局区(存放的变量): 全局变量、静态变量、常量(字符串常量,const修饰的全局常量或变量)
// 数据在程序结束后,由操作系统释放
int g_a = 10;
int g_b = 20;
// const修饰的全局常量
const int c_g_a = 10;
const int c_g_b = 20;
int main()
{
cout << "在全局区中" << 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;
// 常量
// 字符串常量(双引号引起来的) 即"xxxxxxx"
cout << "字符串常量的地址:" << (int)&"hhhhh" << endl;
// const修饰的全局(global)常量
cout << "const修饰的全局常量c_g_a的地址为:" << (int) &c_g_a << endl;
cout << "const修饰的全局常量c_g_b的地址为:" << (int) &c_g_b << endl;
cout << "------------------------------------------------" << endl;
cout << "不在全局区中" << endl;
// 创建普通局部变量(写在函数体内),局部变量存放在栈区
int a = 10;
int b = 20;
cout << "局部变量a的地址为:" << (int)&a << endl;
cout << "局部变量b的地址为:" << (int)&b << endl;
// const修饰的局部(local)常量
const int c_l_a = 10;
const int c_l_b = 110;
cout << "const修饰的局部常量c_l_a的地址为:" << (int)&c_l_a << endl;
cout << "const修饰的局部常量c_l_b的地址为:" << (int)&c_l_b << endl;
return 0;
}
运行结果:
在全局区中
全局变量g_a的地址为:653516800
全局变量g_b的地址为:653516804
静态变量s_a的地址:653516808
静态变量s_b的地址:653516812
字符串常量的地址:653504136
const修饰的全局常量c_g_a的地址为:653503408
const修饰的全局常量c_g_b的地址为:653503412
------------------------------------------------
不在全局区中
局部变量a的地址为:1060109108
局部变量b的地址为:1060109140
const修饰的局部常量c_l_a的地址为:1060109172
const修饰的局部常量c_l_b的地址为:1060109204
2.程序运行后
程序运行后分为栈区和堆区。
01.栈区
由编译器管理开辟和释放, 存放局部变量、函数的形参等。
注意事项:不要返回局部变量的地址或引用
(1).返回局部变量的地址,warning: address of local variable ‘a’ returned
当一个函数返回指向栈内存的地址时,栈上的局部变量会被释放,这意味着返回的地址将指向一个已经不再有效的内存区域。
在后续使用该地址时,相当于访问已经释放的内存,可能会导致程序崩溃或产生不可预测的行为。
(2).返回局部变量的引用,warning: reference to local variable ‘a’ returned
当函数执行完毕后,局部变量 a
将被销毁,这意味着返回的引用将成为悬挂引用 (dangling reference)。这个操作比较危险,会导致数据不可控。
悬挂引用是指指向已经被销毁的内存的引用。
当调用者试图使用这个引用时,会访问一个无效的内存位置,导致未定义的行为,很可能会导致程序崩溃或产生不可预测的结果。
(3).解决方案:
如果函数需要返回一个局部变量的值,可以通过值传递的方式返回,
或者返回一个动态分配的内存对象,可以使用智能指针(如 std::unique_ptr
或 std::shared_ptr
)来管理动态内存的释放。
代码示例:
#include
using namespace std;
// 栈区:局部变量,形参
// 栈区注意事项,不要返回局部变量的地址
// 栈区的数据由编译器管理开辟和释放
int* func(int b) { // 写法一
b = 20;
int a = 10; // 栈上,局部变量存放在栈区,函数执行完后自动释放
return &a; // 返回了局部变量的地址(不建议)
}
int& func1() { // 写法二
int a = 10;
return a; // 返回了局部变量的引用 (不建议)
} // int &temp = a;
int main()
{
int* p = func(1);
cout << *p << endl; // 第一次,编译器做了保留,可以打印正确的值(visual studio里可以正常打印)
cout << *p << endl; // 第二次,这个数据就不会被保留了,相当于是访问已经释放的内存,这是非法操作。
// error 程序运行发生 Segmentation fault,段错误
cout << "-------------" << endl;
int& q = func1();
cout << q << endl; // error 也发生了 Segmentation fault,段错误
return 0;
}
修改后的函数如下,
// 使用值传递
int func(int b) {
b = 20;
int a = 10;
return a; // 返回局部变量的值
}
// 返回动态分配的内存对象
#include
std::unique_ptr func(int b) {
b = 20;
std::unique_ptr ptr = std::make_unique(10);
return ptr; // 返回 std::unique_ptr,它会负责释放内存
}
02.堆区
在C++中主要利用new
关键字,在堆区开辟内存;
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
;若程序员不释放, 程序结束时则由操作系统回收。
语法: new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针。
小总结:
堆区数据由程序员管理开辟和释放
堆区数据利用
new关键字
进行开辟内存
指针是放在栈区(局部变量),指针存放的数据在堆区。
代码示例:
#include
using namespace std;
// 堆区,由程序员来管理
int* func() {
// 利用new关键字,可以将数据开辟到堆区
// 指针是放在栈区(局部变量),指针存放的数据在堆区
// new返回的是该数据类型的指针
int* p = new int(100);
return p;
}
void test1() {
int* x = func();
cout << *x << endl; // 100
cout << *x << endl; // 100
delete x; // 释放堆区数据
// cout << *x << endl; // error 释放的空间不可访问,非法访问
}
void test2() {
// 在堆区利用new开辟数组
int* arr = new int[10]; // 创建一个有10个元素的地址
// 返回对应类型的指针
for (int i = 0; i < 10; i++) {
arr[i] = i + 100;
cout << arr[i] << endl; // 100 ~109
}
// 释放数组
delete[] arr;
}
int main()
{
//test1();
test2();
return 0;
}