C++程序在运行时,操作系统会为其分配一块连续的内存空间,这块空间按照功能不同被划分为栈、堆、全局区和代码区四个部分,每个区域有独立的存储规则和使用场景。

一、代码区
代码区也叫文本段,主要存放程序编译后生成的机器指令,也就是我们编写的函数代码、逻辑语句编译后的二进制内容。这个区域是只读的,防止程序运行时意外修改指令逻辑,同时在多个相同程序运行时可以共享同一份代码区内容,节省内存开销。
比如我们编写的普通函数、类的成员函数,编译后都会存放在代码区,程序执行时CPU会从代码区读取指令依次运行。
二、全局区
全局区也叫静态区,主要存放全局变量、静态变量和常量。这个区域的内容在程序启动时就会分配内存,直到程序结束才会释放,生命周期贯穿整个程序运行过程。
全局区又可以分为两个部分:
- 全局变量和静态变量存储区:存放所有在函数外定义的全局变量,以及用
static关键字修饰的静态局部变量、静态全局变量。 - 常量存储区:存放字符串常量和用
const修饰的全局常量,这部分内容同样是只读的,不能被修改。
下面通过一个示例展示全局区的存储内容:
#include <iostream>
using namespace std;
// 全局变量,存放在全局区
int g_globalVar = 10;
// 静态全局变量,存放在全局区
static int s_staticGlobalVar = 20;
// 全局常量,存放在全局区常量存储区
const int g_constVar = 30;
int main() {
// 静态局部变量,存放在全局区
static int s_staticLocalVar = 40;
cout << "全局变量地址: " << &g_globalVar << endl;
cout << "静态全局变量地址: " << &s_staticGlobalVar << endl;
cout << "全局常量地址: " << &g_constVar << endl;
cout << "静态局部变量地址: " << &s_staticLocalVar << endl;
return 0;
}
三、栈区
栈区由编译器自动分配和释放,主要存放函数的参数值、局部变量等。栈区的分配效率很高,但是空间大小有限,一般只有几兆字节,超过限制会导致栈溢出。
栈区的特点是后进先出,函数被调用时,会在栈顶为该函数分配一块栈帧,存放该函数的局部变量和参数,函数执行结束后,对应的栈帧会被自动释放,里面的内容不再有效。
需要注意的是,不要返回栈区变量的地址,因为函数结束后该地址的内容已经被释放,访问会得到不确定的结果:
#include <iostream>
using namespace std;
// 错误示例:返回栈区变量地址
int* getLocalVarAddr() {
int localVar = 10; // localVar存放在栈区
return &localVar; // 函数结束后localVar被释放,返回的地址无效
}
int main() {
int* p = getLocalVarAddr();
cout << *p << endl; // 访问无效地址,结果不确定
return 0;
}
四、堆区
堆区也叫动态内存区,由开发者手动分配和释放,如果程序结束时没有手动释放,会被操作系统回收。堆区的空间大小远大于栈区,适合存储生命周期较长或者大小不确定的数据。
在C++中,我们使用new关键字在堆区分配内存,使用delete关键字释放内存,分配的内存地址可以传递给其他函数使用,不会像栈区变量那样随函数结束被释放。
堆区内存使用不当很容易出现内存泄漏、野指针等问题,需要特别注意:
#include <iostream>
using namespace std;
int main() {
// 在堆区分配一个int大小的内存,返回地址给p
int* p = new int(10);
cout << "堆区变量值: " << *p << endl;
cout << "堆区变量地址: " << p << endl;
// 使用完后手动释放堆区内存
delete p;
p = nullptr; // 避免野指针
return 0;
}
四个区域对比
我们可以通过下面的表格快速区分四个区域的核心差异:
| 区域名称 | 存储内容 | 分配方式 | 释放方式 | 生命周期 |
|---|---|---|---|---|
| 代码区 | 函数机器指令 | 编译时分配 | 程序结束时释放 | 整个程序运行期间 |
| 全局区 | 全局变量、静态变量、常量 | 编译时分配 | 程序结束时释放 | 整个程序运行期间 |
| 栈区 | 局部变量、函数参数 | 编译器自动分配 | 函数结束时自动释放 | 函数调用期间 |
| 堆区 | 动态分配的数据 | 开发者手动分配 | 开发者手动释放或程序结束 | 手动释放前 |
常见注意事项
- 不要返回栈区变量的地址或引用,避免访问无效内存。
- 堆区内存分配后一定要对应释放,防止内存泄漏,释放后及时将指针置为
nullptr避免野指针。 - 全局区的常量内容是只读的,尝试修改会导致程序崩溃。
- 栈区空间有限,不要定义过大的局部变量数组,避免栈溢出。