在 C++ 中,内存主要被划分为全局区、堆区、栈区、常量区和代码区,每个区域都有其特定的用途和特点。
分区名称 | 作用 | 特点 | 示例 |
---|---|---|---|
全局区 | 存放全局变量和静态变量 | 程序运行期间一直存在,在程序结束时由系统释放内存。全局变量和静态变量的生命周期从程序开始到程序结束。 | int globalVar = 10; static int staticVar = 20; |
堆区 | 由程序员手动分配和释放内存,用于动态内存分配 | 内存分配灵活,但需要程序员手动管理内存,若忘记释放可能导致内存泄漏。 | int* ptr = new int(5); delete ptr; |
栈区 | 存放函数的参数、局部变量等 | 由编译器自动分配和释放,具有先进后出的特点。函数调用结束后,栈上的内存自动被释放。 | void function() { int localVar = 3; } |
常量区 | 存放常量数据,如字符串常量、const 修饰的常量等 | 常量区的内容在程序运行期间不可修改,多个相同的常量在常量区中可能只存储一份。 | const int constVar = 100; char* str = “Hello World”; |
代码区 | 存放程序的可执行代码 | 通常是只读的,用于存储 CPU 执行的机器指令。代码区的大小在程序编译时就已经确定。 | 程序中的函数体、类的成员函数等代码都存放在代码区。 |
1. 全局区(静态区)
知识点
- 存储内容:全局变量和静态变量存放在此区域。全局变量是在函数外部定义的变量,而静态变量是使用 static 关键字修饰的变量,可以是全局静态变量或局部静态变量。
- 生命周期:从程序开始运行到程序结束,全局区的变量一直存在,不会因为函数的调用或返回而销毁。
- 初始化情况:未初始化的全局变量和静态变量存放在全局区的未初始化段(BSS 段),初始化为 0;已初始化的全局变量和静态变量存放在全局区的已初始化段。
示例代码
#include <iostream>
// 全局变量
int globalVar;
// 未初始化的全局变量,存放在 BSS 段
int initializedGlobalVar = 10;
// 已初始化的全局变量,存放在已初始化段
void func() {
// 局部静态变量
static int staticVar;
// 未初始化的局部静态变量,存放在 BSS 段
static int initializedStaticVar = 20;
// 已初始化的局部静态变量,存放在已初始化段
}
int main() {
func();
return 0;
}
注意事项
- 全局变量和静态变量的作用域不同,但都存储在全局区。全局变量的作用域是整个程序,而局部静态变量的作用域仅限于定义它的函数内部。
- 过多使用全局变量会增加程序的耦合性,降低代码的可维护性和可测试性,应尽量避免不必要的全局变量。
2. 堆区
知识点
- 存储内容:通过 new 运算符动态分配的内存块存放在堆区。堆区的内存分配和释放由程序员手动控制。
- 生命周期:由程序员控制,使用 new 分配的内存,必须使用 delete(对于单个对象)或 delete[](对于数组)来释放,否则会导致内存泄漏。
- 分配方式:堆区的内存分配是动态的,分配和释放的时间和顺序可以不固定。
示例代码
#include <iostream>
int main() {
// 动态分配一个整数
int* ptr = new int(5);
std::cout << *ptr << std::endl;
// 释放内存
delete ptr;
// 动态分配一个整数数组
int* arr = new int[10];
// 释放数组内存
delete[] arr;
return 0;
}
注意事项
- 必须确保每次使用 new 分配的内存都有对应的 delete 或 delete[] 操作,避免内存泄漏。
- 堆区的内存分配和释放可能会导致内存碎片问题,影响内存的使用效率。
- 对已经释放的内存再次进行 delete 操作会导致未定义行为,应该避免。
3. 栈区
知识点
- 存储内容:函数的局部变量、函数参数、返回地址等存放在栈区。栈区的内存分配和释放由编译器自动完成。
- 生命周期:与函数的调用和返回相关。当函数被调用时,会在栈上为局部变量和函数参数分配内存;当函数返回时,这些内存会自动释放。
- 分配方式:栈区的内存分配和释放是按照后进先出(LIFO)的原则进行的,类似于栈数据结构。
示例代码
#include <iostream>
void func(int param) {
// 局部变量
int localVar = 20;
std::cout << param << " " << localVar << std::endl;
}
int main() {
int arg = 10;
func(arg);
return 0;
}
注意事项
- 栈区的空间有限,如果递归调用过深或局部变量占用空间过大,可能会导致栈溢出错误。
- 不要返回局部变量的引用或指针,因为局部变量在函数返回后会被销毁,返回的引用或指针将指向无效的内存地址。
4. 常量区
知识点
- 存储内容:存放常量字符串和使用 const 修饰的全局常量。常量区的内容在程序运行期间不可修改。
- 生命周期:从程序开始运行到程序结束,常量区的内容一直存在。
示例代码
#include <iostream>
// 常量字符串
const char* str = "Hello, World!";
// 全局常量
const int constantValue = 100;
int main() {
std::cout << str << " " << constantValue << std::endl;
return 0;
}
注意事项
- 尝试修改常量区的内容会导致未定义行为,应该避免对常量进行修改操作。
- 不同编译器对常量区的实现可能有所不同,但都保证常量的不可修改性。
5. 代码区
知识点
- 存储内容:存放程序的可执行代码,即编译后的机器指令。代码区是只读的,防止程序在运行过程中被意外修改。
- 生命周期:从程序开始运行到程序结束,代码区的内容一直存在。
注意事项
- 代码区的内容由编译器生成,一般不需要程序员手动管理。
- 由于代码区是只读的,任何尝试修改代码区内容的操作都会导致未定义行为。