【C++】内存管理(上)

🫧个人主页:小年糕是糕手

💫个人专栏:《C++》《C++同步练习》《数据结构》《C语言》

🎨你不能左右天气,但你可以改变心情;你不能改变过去,但你可以决定未来!



目录

一、初识内存管理

二、内存管理的核心维度

2.1、内存划分

[一、代码段(Text Segment / 常量区)](#一、代码段(Text Segment / 常量区))

作用:

特点:

[二、数据段(Data Segment / 静态区)](#二、数据段(Data Segment / 静态区))

作用:

特点:

三、堆(Heap)

作用:

特点:

四、栈(Stack)

作用:

特点:

[2.2、实际内存布局示例(Linux x86-64)](#2.2、实际内存布局示例(Linux x86-64))

三、C/C++内存分布

[四、C语言中动态内存管理方式:malloc / calloc / realloc / free](#四、C语言中动态内存管理方式:malloc / calloc / realloc / free)

[4.1、malloc / calloc / realloc的区别](#4.1、malloc / calloc / realloc的区别)

4.2、malloc的实现原理(了解)


一、初识内存管理

内存管理是操作系统(OS)和编程语言运行时核心功能之一,本质是高效、安全地分配、使用、回收计算机内存资源,避免内存泄漏、野指针、内存碎片等问题,确保程序稳定运行且资源利用率最大化。

我们为什么需要内存管理?

一、解决物理限制问题

  • 内存稀缺:物理内存有限,但程序需求很大

  • 多任务需求:需要同时运行多个程序

二、提供关键功能

  • 隔离保护:防止程序互相干扰或攻击

  • 地址抽象:让程序以为自己独占整个内存空间

  • 内存共享:安全地共享代码库和公共数据

三、提升系统能力

  • 扩展内存:虚拟内存让程序能用比物理内存更大的空间

  • 效率优化:通过缓存、预取等技术加速访问

  • 动态管理:按需分配,避免浪费

一句话总结:内存管理让有限的内存资源能被多个程序安全、高效地共享使用,是现代计算系统能够工作的基础。

二、内存管理的核心维度

2.1、内存划分

我们以进程的角度来进行内存划分:

一、代码段(Text Segment / 常量区)
作用:
  • 存储程序的执行代码:机器指令、函数体等

  • 包含只读的常量:字符串常量、const修饰的全局变量

特点:
  1. 只读性:防止程序意外修改自身代码

  2. 可共享:多个进程可以共享同一份代码副本(如多个实例运行同一程序)

  3. 大小固定:编译链接后确定,运行时不变

  4. 位于内存低地址区域

二、数据段(Data Segment / 静态区)
作用:
  • 存储全局变量和静态变量

    • 初始化的全局变量(全局区)

    • 未初始化的全局变量(BSS段)

    • 静态局部变量和静态全局变量

特点:
  1. 生命周期最长:从程序启动到结束一直存在

  2. 自动初始化

    • 显式初始化的变量:初始值在编译时确定

    • 未显式初始化的变量:系统自动初始化为0(或NULL)

  3. 位于代码段之上

  4. 大小在编译时确定

三、堆(Heap)
作用:
  • 动态内存分配区域

  • 存储mallocnewcallocrealloc等分配的内存

特点:
  1. 动态性:大小在运行时动态变化

  2. 手动管理

    • 程序员负责申请和释放内存

    • 可能产生内存泄漏(未释放)或悬空指针

  3. 向上增长:向高地址方向扩展

  4. 碎片化风险:频繁分配释放可能导致内存碎片

  5. 分配速度相对较慢:需要寻找合适大小的空闲块

四、栈(Stack)
作用:
  • 存储函数调用上下文和局部变量

    • 函数参数

    • 返回地址

    • 局部变量

    • 函数调用的寄存器保存区

特点:
  1. 自动管理:编译器自动分配和释放

  2. 后进先出(LIFO):函数调用和返回符合栈的特性

  3. 向下增长:向低地址方向扩展

  4. 速度快:只需移动栈指针即可分配内存

  5. 大小有限:通常较小(Linux默认8MB),可能发生栈溢出

  6. 生命周期短:随函数调用开始,随函数返回结束

对比表格

特性 数据段 代码段
存储内容 局部变量、函数参数、返回地址 动态分配的内存 全局/静态变量 程序代码、常量
管理方式 自动(编译器) 手动(程序员) 自动(系统) 自动(系统)
生命周期 函数调用期间 直到显式释放 整个程序运行期间 整个程序运行期间
大小 固定且较小 动态变化,理论上受限于系统 编译时确定 编译时确定
生长方向 向下(低地址) 向上(高地址) - -
访问速度 最快 较慢
线程共享 每个线程独立 进程内共享 进程内共享 进程内共享
2.2、实际内存布局示例(Linux x86-64)

text

复制代码
高地址
┌─────────────────┐
│   内核空间      │
├─────────────────┤
│   栈 (向下增长)  │ ← 栈指针(SP)
│    ...          │
├─────────────────┤
│    ...          │
├─────────────────┤
│   堆 (向上增长)  │ ← 堆指针(brk)
│    ...          │
├─────────────────┤
│   未初始化数据段(BSS)│
├─────────────────┤
│   已初始化数据段    │
├─────────────────┤
│   代码段         │
└─────────────────┘
低地址

编程注意事项

  1. 栈溢出:避免大局部变量或深度递归

  2. 堆管理 :确保配对使用malloc/freenew/delete

  3. 数据段:过度使用全局变量会增加内存占用

  4. 代码段:常量字符串在此区域,不可修改

三、C/C++内存分布

我们先来看下面的一段代码和相关问题

cpp 复制代码
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";
	const 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);
}

选择题:

选项:A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)

globalVar在哪里?____

staticGlobalVar在哪里?____

staticVar在哪里?____

localVar在哪里?____

num1 在哪里?____

答案:C、C、C、A、A

解析:

我们根据表格可以看出,也可以看下面这个表格:

分区 作用 特点
栈(Stack) 存储局部变量、函数调用上下文 自动分配 / 释放(栈帧)、连续内存、大小固定(几 MB)
堆(Heap) 动态分配内存(如 new/malloc) 手动 / 自动回收、非连续、大小灵活(可达 GB 级)
全局 / 静态区 存储全局变量、静态变量 程序启动分配、退出释放
常量区 存储字符串常量、const 变量 只读、程序退出释放
代码区 存储程序指令(二进制) 只读、可共享

char2在哪里?____

*char2在哪里?___

pChar3在哪里?____

*pChar3在哪里?____

ptr1在哪里?____

*ptr1在哪里?____

答案:A、A、A、D、A、B

解析:

我们首先来看char2,他是一个数组,我们知道数组名代表整个数组那他就是在栈上;pchar3大家可能都觉得他有个const修饰那他应该在常量区,这个const修饰的是指向内容,并不修饰本身,所以他也在栈上;数组名代表整个数组,这里char2解引用就代表首元素的地址解引用(实际就是a),这个数组在栈上,所以解引用也是在栈上;pChar3也在栈上,解引用找他的指向内容,他指向这个常量字符串,这个常量字符串在常量区,所以*pChar也在常量区;我们来总结一下其实就是在常量区有个"abcd\0",我们在栈上定义一个数组char2,我们将常量区的"abcd\0"拷贝一份给他,在栈上再定义一个局部的指针变量,这个局部的指针变量指向这个字符串首元素的地址,当我们解引用就是常量区的"abcd\0"的a,给出图解如下:

常量区的数据不可以修改!

我们在栈上定义了一指针变量ptr1,然后malloc一块空间,指针指向那快空间,然后那块空间在堆区上,所以解引用*ptr1就在堆区上;

  • ptr1Test()函数内的局部指针变量 ,局部变量会存储在栈区(函数调用时分配栈帧,函数结束后自动释放)。

  • ptr1是通过malloc动态分配内存得到的指针,malloc会从堆区 申请内存空间;*ptr1ptr1指向的内存空间,因此存储在堆区 (需手动调用free释放)。

【说明】

  1. 栈又叫堆栈 -- 非静态局部变量 / 函数参数 / 返回值等等,栈是向下增长的。
  2. 内存映射段是高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux 课程如果没学到这块,现在只需要了解一下)
  3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
  4. 数据段 -- 存储全局数据和静态数据。
  5. 代码段 -- 可执行的代码 / 只读常量。

四、C语言中动态内存管理方式:malloc / calloc / realloc / free

4.1、malloc / calloc / realloc的区别

大家要是想详细了解可以移步我以前写过的一篇博客:【C语言】C 动态内存管理全解析:malloc/calloc/realloc 与柔性数组实战-CSDN博客https://blog.csdn.net/2501_91731683/article/details/150438447这里我们简单为大家讲解一下大致的区别:

malloc是动态内存开辟但是不会初始化;calloc是动态内存开辟并且初始化为0;realloc是在原有的动态内存空间上去扩容(如果原有空间后没有足够连续内存,会重新分配一块更大的内存,把原数据拷贝过去,再释放原内存)

cpp 复制代码
void Test()
{
	int* p2 = (int*)calloc(4, sizeof(int));
	int* p3 = (int*)realloc(p2, sizeof(int) * 10);
    cout << p2 << endl;
    cout << p3 << endl;

	// 这里需要free(p2)吗?
	free(p3);
}

这里的p2不需要free,这里的realloc可能原地扩容也可能异地扩容,原地扩容他们俩地址是一样的,释放一次就可以了,要是异地扩容也不需要我们去释放p2,因为realloc申请一块新的空间然后进行拷贝原空间,结束后会释放原空间,再把新空间返回给p3。

4.2、malloc的实现原理(了解)

这部分现阶段我们并不做重点去了解,大家要是感兴趣可以自行去学习了解,等我们学的多了再回头来看这些,这里有个b站的视频链接,大家感兴趣可以去学习:

gilbc中malloc实现原理https://www.bilibili.com/video/BV117411w7o2/?spm_id_from=333.788.videocard.0


相关推荐
小猪快跑爱摄影3 小时前
【AutoCad 2025】【C#】零基础教程(一)——Rider 构建 HELLO 插件
开发语言·c#
shenzhenNBA3 小时前
如何用python生成一定规则的序列号?
开发语言·python·sn·序列号
Qiuner3 小时前
Spring Boot 机制五: Bean 生命周期与后置处理器(BeanPostProcessor)源码深度剖析
java·spring boot·后端
路边草随风3 小时前
java实现发布flink yarn session模式作业
java·flink·yarn
csdn_aspnet3 小时前
C++ 长方体表面积和体积计算程序(Program for Surface Area and Volume of Cuboid)
c++
沐知全栈开发3 小时前
SQL CHECK约束的使用与优势
开发语言
qq_12498707533 小时前
基于Spring Boot的阳光餐盘点餐系统(源码+论文+部署+安装)
java·vue.js·spring boot·后端·毕业设计
晚风(●•σ )3 小时前
【数据结构】——算法设计范式的相关习题
数据结构·算法·贪心算法·深度优先·动态规划·剪枝·广度优先
csbysj20203 小时前
Highcharts 散点图
开发语言