C++内存管理详解:各类变量的存储区域

news2024/9/20 14:24:30

    在C++中,变量的存储位置取决于它们的类型和生命周期。那么不同的各个变量究竟存储在哪个区域呢?

1.不同类型的变量

我们首先从变量类型的不同来说明:

1. 全局变量和静态变量 

- 存储区:全局/静态区(静态区)
- 说明:全局变量(包括文件级和函数级的)和使用 `static` 关键字声明的变量存储在静态区中。它们在程序启动时被分配,并在程序结束时销毁,具有整个程序的生命周期。

int globalVar; // 全局变量
int main()
{
     static int staticVar; // 静态变量
     return 0;
}

2. 局部变量

- 存储区:栈区(Stack)
- 说明:局部变量是在函数或代码块内部声明的变量。它们在函数执行时分配,在函数退出后销毁。栈区的分配和释放速度很快,但栈区的大小有限。

    void func() {
      int localVar; // 局部变量
    }

3. 动态分配的变量

- 存储区:堆区(Heap)
- 说明:通过 new、malloc等动态分配的变量存储在堆区中。堆区的内存由程序员手动管理,使用 new分配后需要手动 delete,malloc分配后需要 free释放,否则可能会导致内存泄漏。

    int* p = new int; // 动态分配的变量
    delete p;
    指针变量p作为局部变量存储在栈区
    ,p通过new分配的内存空间在堆区

4. 常量

- 存储区:常量存储区(常量区)
- 说明:字符串常量和const修饰的全局变量等通常存储在常量区。这个区域是只读的,无法修改。

    const int constVar = 100; // 常量
constVar是一个const修饰的局部变量,存储在栈区,100存储在
程序的只读数据段(常量区)
    const char* str = "Hello"; // 字符串常量
str是一个局部变量,存储在栈区,它保存的是字符串常量“hello”
的地址,“hello”存储在产量区(也称只读数据区),因为他是不
可修改的字符串的字面值

5. 函数参数

- 存储区:栈区(Stack)
- 说明:函数参数在函数调用时压入栈中,当函数返回时这些参数被销毁。

6. 静态局部变量

- 存储区:全局/静态存储区(静态区)
- 说明:静态局部变量只在函数中初始化一次,并在整个程序运行期间保留它的值,但它的作用域仍然在定义它的函数中。

    void func() {
      static int staticLocalVar = 0; // 静态局部变量
    }

2.内存分配区域

我们再从内存分配的5个主要区域来说明:
1. 栈区(Stack Segment):局部变量、函数参数、返回地址。
2. 堆区(Heap Segment):动态分配的内存。
3. 全局/静态存储区(Data Segment):
   - 已初始化数据段:已初始化的全局变量和静态变量。
   - 未初始化数据段:未初始化的全局变量和静态变量。
4. 常量区(Text/ROData Segment):字符串常量、`const` 修饰的全局常量。
5. 代码区(Code Segment or Text Segment):存储程序的可执行代码。

接下来我们来详细介绍这五块区域:

 1. 栈区(Stack Segment)

    - 特点:用于存储局部变量函数参数返回地址。栈上的内存分配是自动管理的,即由编译器在函数调用时自动分配并在函数返回时自动释放。
   - 分配方式:由系统自动分配和释放,遵循后进先出(LIFO)的原则。
   - 优点:分配和释放速度非常快。
   - 缺点:栈空间有限,局部变量和函数调用较多时可能会导致栈溢出。
   - 典型使用:局部变量、函数参数。

#include <iostream>
using namespace std;

void func(int param) {
	// 局部变量,存储在栈区
	int localVar = 10;

	// 输出局部变量和参数的地址
	cout << "Address of localVar (栈区局部变量): " << &localVar << endl;
	cout << "Address of param (栈区函数参数): " << &param << endl;
}

int main() {
	// 局部变量,存储在栈区
	int mainVar = 20;

	// 调用函数 func,并传递一个参数
	func(mainVar);

	// 输出 mainVar 的地址
	cout << "Address of mainVar (栈区局部变量): " << &mainVar << endl;

	return 0;
}

(1)函数参数存储在栈区:

 - 当调用 func(mainVar) 时,参数mainVar的值会被压入栈中,并作为 param传递给 func 函数。此时,param是栈区中的一个局部变量。

(2)局部变量存储在栈区:
   - 在 func 函数内部,localVar是一个局部变量,它也存储在栈区中。这个变量会随着函数调用被压入栈中,并在函数返回时从栈中移除。
   - 同样,在 main 函数中,mainVar是局部变量,存储在栈区。

(3)自动管理:
   - func函数中的局部变量 localVar 参数 param在函数返回后会自动从栈中销毁,内存被自动释放。
   - mainVar 也是局部变量,当 main函数结束时,它的内存也会自动从栈中销毁。

### 输出示例(地址仅为示例):


当函数调用时,局部变量和参数会被压入栈中,函数返回时栈上的这些变量会被自动销毁。

从输出中可以看到,栈中的局部变量和函数参数的内存地址是相邻的(栈通常是从高地址向低地址分配的),表明它们在栈区中分配。栈区的特点是遵循后进先出(LIFO)的原则,因此随着函数调用和返回,栈上的变量也会相应地分配和释放。

2. 堆区(Heap Segment)

   - 特点:用于动态内存分配,程序员可以使用 new或 malloc 手动分配内存,并且需要使用 delete 或 free来释放。
   - 分配方式:动态分配,由程序员控制内存的分配和释放。
   - 优点:堆的空间非常大,可以根据需要动态调整分配的内存量。
   - 缺点:分配和释放效率相对较慢,如果程序员忘记释放内存,会导致内存泄漏。频繁分配和释放可能会导致内存碎片。
   - 典型使用:动态分配对象或数组

#include <iostream>
#include <iomanip>
#include <assert.h>
using namespace std;

#include <iostream>

class MyClass {
public:
	MyClass() {
		cout << "创建对象" << endl;
	}
	~MyClass() {
		cout << "销毁对象" << endl;
	}
};

int main() {
	// 在堆区动态分配一个整数
	int* pInt = new int; //动态分配,存储在堆区
	*pInt = 42;
	cout << "堆区中的值: " << *pInt << endl;
	cout << "堆区地址: " << pInt << endl;

	// 在堆区动态分配一个对象
	MyClass* pObj = new MyClass();// 动态分配对象,存储在堆区

	// 动态分配一个数组
	int* pArr = new int[5];// 在堆区分配一个数组
	for (int i = 0; i < 5; ++i) 
	{
		pArr[i] = i * 10;
		cout << "pArr[" << i << "] = " << pArr[i] << endl;
	}

	// 释放堆区分配的内存
	delete pInt;// 释放动态分配的整数
	delete pObj;// 释放动态分配的对象
	delete[] pArr;// 释放动态分配的数组

	return 0;
}

代码说明:

(1)动态分配一个整数:

   - pInt本身的存储位置:pInt 是一个局部变量,存储在栈区。它是一个指针,存储着在堆区分配的那块内存的地址,这块地址是指向堆区的。

   - new int 分配的内存的存储位置:new int 在堆区动态分配了一块 int 类型的内存,这块内存存储在堆区。该堆区内存存放了 42 这个值。通过 pInt 指针,可以访问并修改这块堆区内存的内容。

   - 手动释放:使用 delete pInt; 来释放这块堆区的内存,否则会导致内存泄漏。

(2)动态分配一个对象:
   - pObj 本身的存储位置:pObj 是一个局部指针变量,存储在栈区。它保存着 new MyClass() 在堆区动态分配的 MyClass 对象的地址,只是一个指向堆区内存的指针变量。

   - new MyClass() 创建的对象的存储位置:new MyClass() 在堆区创建了一个 MyClass 类型的对象,该对象存储在堆区。pObj 指向这个在堆区创建的 MyClass 对象,程序员可以通过 pObj 访问和操作堆区中的该对象。


   - 手动释放:使用 delete pObj; 来调用析构函数并释放对象占用的内存。

(3)动态分配一个数组:
   - pArray本身的存储位置:pArray 是一个局部指针变量,存储在栈区。它保存着 new int[5] 分配的数组的首地址,即堆区中分配的数组的起始地址。

   - new int[5]分配的数组的存储位置:new int[5] 在堆区动态分配了一个长度为 5 的 int 数组,该数组存储在堆区。数组的所有元素存储在堆区中,可以通过 pArray 来访问和修改这些元素。


   - 手动释放:对于动态分配的数组,必须使用 delete[] pArray;来释放内存,使用普通的 delete 会导致未定义行为。

堆区的优点与缺点:
- 优点:堆区可以在运行时动态分配内存,灵活管理内存,适用于需要大量数据或者需要灵活调整大小的情况。
- 缺点:堆区的分配和释放需要程序员手动管理,容易出现内存泄漏或者内存释放错误,且分配和释放速度相对栈区较慢。

3. 全局/静态存储区(Data Segment)

   - 特点:用于存储全局变量静态变量(无论是否被static修饰)。全局变量和静态变量在程序运行时分配,并在程序结束时释放。
  - 分为两部分:
     - 已初始化数据段:存储已初始化的全局变量、静态变量。
     - 未初始化数据段:存储未初始化的全局变量和静态变量。未初始化的全局/静态变量在程序开始时自动初始化为零。
  - 分配方式:在程序启动时由系统分配,并在程序结束时释放。
  - 典型使用:全局变量、静态局部变量。

#include <iostream>
using namespace std;

// 全局变量,存储在静态区
int globalVar = 100;

// 静态变量,存储在静态区
void staticFunc() {
    static int staticVar = 10;  // 静态变量,存储在静态区
    staticVar++;
    cout << "静态变量: " << staticVar << endl;
}

int main() {

    // 调用函数,修改静态变量
    staticFunc();
    staticFunc();

    return 0;
}

   - 全局变量 globalVar的存储位置:globalVar 是一个全局变量,存储在静态区的已初始化数据段。它在程序开始时被初始化为 100,并且直到程序结束时才被销毁。

   - 静态变量 staticVar的存储位置:staticVar 是函数 staticFunc 中的静态变量,存储在静态区的已初始化数据段。与普通局部变量不同,静态变量的生命周期贯穿整个程序运行期间,即使函数结束后,静态变量的值也会保留。

   - 常量 constVar 和字符串字面量:存储位置:constVar 和 "Hello, World!" 是常量,存储在静态区的常量区。常量在程序运行期间不会改变,且常量区内存是只读的。局部变量 localVar:存储位置:localVar 是局部变量,存储在栈区,它在 main 函数运行时创建,并在函数结束时被销毁。 

4. 常量区

   - 特点:用于存储常量数据(如字符串常量和const修饰的全局常量)。这部分内存是只读的,试图修改它将会导致运行时错误。
   - 分配方式:由系统在程序启动时分配,并在程序结束时释放。
   - 典型使用:字符串常量const修饰的全局常量
 

#include <iostream>

// const 全局常量,存储在常量区
const int constGlobalVar = 42;

int main() {
    // 字符串字面量,存储在常量区
    const char* str = "Hello, World!";

    // const 局部常量,通常存储在栈区,但有时编译器也会将其优化到常量区
    const int constLocalVar = 100;

    // 输出全局常量和字符串字面量
    cout << "全局常量:" << constGlobalVar << endl;
    cout << "字符串常量: " << str << endl;

    // 输出局部常量
    cout << "局部常量:" << constLocalVar << endl;

    return 0;
}

   - constGlobalVar:这是一个全局常量,存储在常量区,其值在程序运行期间不可修改。

   - 字符串字面量 "Hello, World!":这是一个字符串字面量,编译时存储在常量区,程序运行期间同样不可修改。

   - constLocalVar:这是一个局部常量,通常情况下它存储在栈区,但在某些情况下,编译器可能会将它优化到常量区,特别是当它不被修改且是只读时,这取决于编译器。

5. 代码区

   - 特点:用于存储程序的可执行代码,也称为文本段,程序的所有函数和方法的代码,包括 `main函数、全局函数、成员函数、静态成员函数等,都是在代码区内存储并执行的。都存储在代码区。该区域通常是只读的,防止程序在运行时修改其自身的代码。
   - 分配方式:由系统在程序加载时分配,并在程序结束时释放,且不会动态增加或减少。
   - 典型使用:存储编译好的指令函数代码

代码区的存储内容:
- 函数体的机器指令。
- 静态成员函数。
- 内联函数(在某些情况下,编译器也会将内联函数放在代码区中)。

#include <iostream>

// 函数存储在代码区
void functionInCodeSegment() {
    std::cout << "This is a function stored in the code segment." << std::endl;
}

int main() {
    // main 函数也存储在代码区
    std::cout << "Hello, World! This is the code segment." << std::endl;
    
    // 调用函数,函数体存储在代码区
    functionInCodeSegment();
    
    return 0;
}

代码说明:
1. main 函数:main 函数的代码存储在代码区。当程序运行时,操作系统会加载并执行代码区中的指令。
2. functionInCodeSegment函数:同样地,这个函数的机器指令也存储在代码区中。每次调用该函数时,程序从代码区中读取并执行对应的指令。

内存布局:

+---------------------------+
|       代码区 (只读)        |
|---------------------------|
|  main 函数的指令           |  --> 存储程序的可执行指令
|  functionInCodeSegment 的指令|
+---------------------------+
|       数据区 (可读写)      |
|  静态区、全局变量、常量区  |  --> 全局变量、静态变量、常量等
+---------------------------+
|       堆区                |
|  动态分配的内存 (new/malloc)| --> 动态分配的数据
+---------------------------+
|       栈区                |
|  局部变量、函数调用信息    | --> 函数调用栈帧、局部变量
+---------------------------+
```

     每个区域都有不同的管理方式和生命周期,合理管理这些内存区域对程序的性能和稳定性至关重要。

通过上面的学习,我们再来看一段代码,检验一下学习成果吧:

C c;               // 全局变量,存储在静态区
void main()
{
  A* pa = new A();  // 动态分配,存储在堆区
  B b;              // 栈上分配,存储在栈区
  static D d;       // 静态局部变量,存储在静态区
  delete pa;        // 回收堆区的内存
}
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
    static int staticVar = 1;
    int localVar = 1;
    int num1[10] = {1, 2, 3, 4};
    char char2[] = "abcd";
    char* pChar3 = "abcd";
    int* ptr1 = (int*)malloc(sizeof (int)*4);
    int* ptr2 = (int*)calloc(4, sizeof(int));
    int* ptr3 = (int*)realloc(ptr2, sizeof(int)*4);
    free (ptr1);
    free (ptr3);
}

以下对代码中的各类变量进行分析以确定其所在区域:
1.  globalVar 是全局变量,存储在数据段(静态区)。
2.  staticGlobalVar 也是全局静态变量,同样存储在数据段(静态区)。
3.  staticVar 是静态局部变量,存储在数据段(静态区)。
4.  localVar 是局部变量,存储在栈区。
5.  num1 是局部数组,存储在栈区。
6.  char2 是局部字符数组,存储在栈区。
7.  pChar3 是指针变量,指向字符串常量,字符串常量存储在数据段(静态区),所以 pChar3 本身作为局部变量存储在栈区。
8.  ptr1 、 ptr2 、 ptr3 是指针变量,作为局部变量存储在栈区,它们通过 malloc 、 calloc 、 realloc 分配的内存空间在堆区。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2142324.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习----------------------文本预处理

目录 文本预处理读取数据集词源化词表该部分总代码该部分总代码 整合所有功能该部分总代码 文本预处理 文本预处理&#xff1a;把文本当作一个时序序列 将解析文本的常见预处理步骤。 这些步骤通常包括&#xff1a; ①将文本作为字符串加载到内存中。 ②将字符串拆分为词元&…

Redis的存储原理和数据模型

一、Redis是单线程还是多线程呢&#xff1f; 我们通过跑redis的代码&#xff0c;查看运行的程序可以得知&#xff0c;Redis本身其实是个多线程&#xff0c;其中包括redis-server&#xff0c;bio_close_file&#xff0c;bio_aof_fsync&#xff0c;bio_lazy_free&#xff0c;io_t…

Python如何导入自定义包?

在 Python 中&#xff0c;导入包是日常开发的基础操作之一。Python 通过其模块化设计&#xff0c;使得代码可以组织成模块和包&#xff0c;提升了代码的复用性和可维护性。而当开始构建复杂的Python项目时&#xff0c;通常会发现将代码组织在各种模块和包中是非常有帮助的。自定…

【C++】list常见用法

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;C从小白到高手 &#x1f339;往期回顾&#x1f339;&#xff1a;[C]vector常见用法 &#x1f516; 流水不争&#xff0c;争的是滔滔不息。 文章目录 一、list的介绍li…

JVM 调优篇6 可视化性能监控工具-JVisual VM

一 Visual VM 1.1 概述 Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具。 它集成了多个JDK命令行工具&#xff0c;使用Visual VM可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo)&#xff0c;监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat…

Celery的使用

Celery 一、Celery概述1. 特点:2. celery组成3. 安装与使用4. 邮箱配置二、Celery的使用实操——发送邮件1. 安装2. 配置一、Celery概述 1. 特点: 2. celery组成 配置任务队列Broker,采用redis保存要执行的任务队列 Client:任务的发出者 Worker:任务的处理者 3. 安装与使用…

从0-1 用AI做一个赚钱的小红书账号(不是广告不是广告)

大家好&#xff0c;我是胡广&#xff01;是不是被标题吸引过来的呢&#xff1f;是不是觉得自己天赋异禀&#xff0c;肯定是那万中无一的赚钱天才。哈哈哈&#xff0c;我告诉你&#xff0c;你我皆是牛马&#xff0c;不要老想着突然就成功了&#xff0c;一夜暴富了&#xff0c;瞬…

信奥初赛解析:1.2-计算机系统的基本结构

目录 知识要点 一、概述 二、计算机硬件系统 &#xff08;一&#xff09;处理器 &#xff08;二&#xff09;存储器 (1)内存储器 (2)外存储器 ①固态硬盘存储器 ②机械硬盘存储器 ③闪存 (三)输入设备 (四)输出设备 (五)总线结构 (六)主要的性能指标 1.字长 2.运…

计算机毕业设计 网上书店系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

JVM OutOfMemoryError 与 StackOverflowError 异常

目录 前言 堆溢出 虚拟机栈和本地方法栈溢出 方法区溢出 前言 JVM规范中规定, 除了程序计数器之外, 其他的运行时数据区域, 例如堆栈, 方法区, 都会出现OutOfMemoryError异常. 那么到底是怎么样的代码, 才会引起堆溢出, 栈溢出, 或者是方法区的溢出呢? 如果遇到了又该如何…

书生大模型全链路开源体系,学习

优点 书生浦语开源大模型&#xff0c;是一个开源的大模型&#xff0c;大家可以一起学习 还有配套的教学视频&#xff0c;很快就能上手&#xff0c;而且还奖励算力&#xff0c;可以直接训练&#xff0c;讨论学习&#xff0c;非常nice。 教学视频 书生浦语大模型全链路开源开…

FastAPI与环境变量:实现无缝切换与高效运维

在现代软件开发中&#xff0c;尤其是构建RESTful API时&#xff0c;环境变量的管理显得尤为重要。它们不仅允许我们在不同环境中&#xff08;如开发、测试、生产&#xff09;灵活地调整应用的行为&#xff0c;还极大地增强了应用的安全性和可维护性。FastAPI作为一个新兴的、高…

ROS组合导航笔记1:融合传感器数据

使用机器人定位包&#xff08;robot_localization package&#xff09;来合并来自不同传感器的数据&#xff0c;以改进机器人定位时的姿态估计。 基本概念 在现实生活中操作机器人时&#xff0c;有时我们需要处理不够准确的传感器数据。如果我们想要实现机器人的高精度定位&am…

苍穹外卖 修改nginx的端口后websocket连接失败解决

苍穹外卖 修改nginx的端口后websocket连接失败解决 问题&#xff1a; 后端配置好websocket后前端仍显示如图所示的错误 解决&#xff1a; 先用websocket在线工具测试后端是否能正常连接&#xff08;这个基本上不会出现问题&#xff09;用f12观察前端发送的请求 正常来说这个请…

chatgpt个人版ssrf漏洞

文章目录 免责申明搜索语法漏洞描述漏洞复现修复建议 免责申明 本文章仅供学习与交流&#xff0c;请勿用于非法用途&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任 搜索语法 fofa title"ChatGPT个人专用版"漏洞描述 该系统是一个开源的…

【两方演化博弈代码复现】:双方演化博弈的原理、概率博弈仿真、相位图、单个参数灵敏度演化

目录-基于MatLab2016b实现 一、演化博弈的原理1. 基本概念2. 参与者的策略3.演化过程 二、MATLAB 代码解读&#xff08;博弈参与主体&#xff08;双方&#xff09;策略选择的动态演化讨程&#xff09;三、MATLAB 代码解读&#xff08;博弈主体随着时间策略选择的动态演化讨程&a…

若依nday复现

前言 声明&#xff1a;此文章仅做学习&#xff0c;未经授权严禁转载。请勿利用文章内的相关技术从事非法测试&#xff0c;如因此产生的一切不良后果与文章作者无关 本文章只做简单汇总&#xff0c;在此感谢其他师傅的文章和分享 前置准备 环境搭建 下载&#xff1a;https:/…

访谈心脑血管名医黄力医生:医术精湛,心系患者

黄力医生&#xff0c;一位在心脑血管领域深耕多年的杰出医者&#xff0c;其医学之路同样始于对国内顶尖医学院校的刻苦钻研。在那里&#xff0c;她不仅打下了坚实的医学理论基础&#xff0c;更培养了对医学事业的无限热爱与崇高追求。毕业后&#xff0c;黄力医生毅然选择了心脑…

django-prometheus使用及源码分析

简介 在django服务运行过程中&#xff0c;希望可以对其获取promethues指标进行监控&#xff0c;这样可以实时知道其运行状态&#xff0c;当它运行异常时可以及时进行告警&#xff0c;并且帮助我们可以对其针对性进行优化。比如请求量过大是否要进行限流或者扩容&#xff0c;再…

【黄力医生】血栓隐患大排查:七类人群如何自我监测静脉血栓风险

血栓&#xff0c;这一看似无声无息的健康杀手&#xff0c;实则潜藏着巨大的风险。静脉血栓作为血栓的一种常见类型&#xff0c;其形成与多种因素密切相关&#xff0c;并可能引发严重的并发症&#xff0c;如肺栓塞等。黄力医生指出&#xff0c;有七类人群特别需要关注自身静脉血…