首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++】C/C++ 内存管理从入门到进阶

【C++】C/C++ 内存管理从入门到进阶

作者头像
用户11872857
发布2025-12-17 16:16:33
发布2025-12-17 16:16:33
2270
举报

前言

📋 本文围绕 C/C++ 内存管理核心知识点,从分区、分配机制、实战对比到避坑技巧,帮开发者系统性掌握精髓,规避内存泄漏等常见问题。

📚 C++初阶

-【 C++发展史、命名空间、输入输出、缺省参数、函数重载 】

-【 C++引用、内联函数、auto、范围 for、nullptr 】

-【 类和对象(上篇)】

-【 类和对象(中篇)】

-【 C++const成员与日期类 】

-【 类和对象(下篇)】



一、C/C++ 内存分布

C/C++ 程序运行时,内存会被划分为多个功能不同的区域,不同区域的数据在存储特性、生命周期上差异显著。

【C/C++中程序内存划分图】

【说明】undefined

  1. 又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的(先使用高地址的内存)。
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(现在只需要了解一下,后续Linux会讲到)
  3. 用于程序运行时动态内存分配,堆是可以上增长的(先使用低地址的内存)。
  4. 数据段--存储全局数据和静态数据。
  5. 代码段--可执行的代码(逻辑代码)/只读常量。
  6. 局部的 const 修饰的普通变量存在栈区,局部的字符串常量存在代码段(语法这样规定)~
【相关题目】
代码语言:javascript
复制
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);

}

1. 选择题:

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

globalVar在哪里? C staticGlobalVar在哪里?__C__ staticVar在哪里?__C__ localVar在哪里?__A__ num1 在哪里?__A__ 【分析】:

  • globalVar全局变量在数据段
  • staticGlobalVar静态全局变量在静态区
  • staticVar静态局部变量在静态区
  • localVar局部变量在栈区
  • num1局部变量在栈区

char2在哪里?__A__ *char2在哪里?__A__

pChar3在哪里?__A__ *pChar3在哪里?__D__

ptr1在哪里?__A__ *ptr1在哪里?__B__

【分析】:

  • char2局部变量在栈区
  • char2是一个数组,把后面常量串拷贝过来到数组中,数组在栈上,所以*char2在栈上
  • pChar3局部变量在栈区
  • *pChar3得到的是字符串常量字符在代码段
  • ptr1局部变量在栈区
  • *ptr1得到的是动态申请空间的数据在堆区

2. 填空题:

sizeof(num1) = __40__; //数组大小,10个整形数据一共40字节 sizeof(char2) = __5__; //包括\0的空间 strlen(char2) = __4__; //不包括\0的长度 sizeof(pChar3) = __4__; //pChar3为指针 strlen(pChar3) = __4__; //字符串“abcd”的长度,不包括\0的长度 sizeof(ptr1) = __4__; //ptr1是指针

二、栈与堆对比

栈和堆是日常开发中最常接触的内存区域,二者的差异直接决定了使用场景的选择,以下是全方位对比:

特性

栈区(Stack)

堆区(Heap)

管理方式

编译器自动分配释放

程序员手动通过new/delete等接口管理

空间大小

较小(几 MB)

较大(受系统内存限制)

访问速度

极快(栈指针操作)

较慢(需内存寻址)

生命周期

随作用域自动销毁

显式释放前持续存在

内存碎片

频繁分配释放易产生碎片

增长方向

从高地址到低地址

从低地址到高地址

典型场景

局部变量、函数参数

大对象、跨作用域数据

风险点

栈溢出(过大局部变量)

内存泄漏(未释放)、野指针

三、动态内存管理

C 和 C++ 都支持堆内存的动态管理,但提供了不同的接口,使用时需注意语法规范和匹配原则,避免混用导致内存问题。

1. C语言:malloc/calloc/reallocfree

  • malloc(size_t size):分配指定字节数的堆内存,返回 void * 指针,需手动类型转换。
  • calloc(size_t num, size_t size):分配 num 个大小为 size 的连续内存,初始化为 0。
  • realloc(void* ptr, size_t size):调整已分配内存的大小,可能会移动内存块。
  • free(void* ptr):释放通过上述函数分配的堆内存,ptr 必须指向堆内存起始地址。

【示例代码】:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

int main() 
{
    // 1. malloc:分配内存(不初始化)
    int* m = (int*)malloc(2 * sizeof(int));
    m[0] = 1; m[1] = 2;
    printf("malloc: %d, %d\n", m[0], m[1]);


    // 2. calloc:分配并初始化为0
    int* c = (int*)calloc(2, sizeof(int));
    printf("calloc: %d, %d\n", c[0], c[1]);


    // 3. realloc:扩容malloc的内存
    m = (int*)realloc(m, 3 * sizeof(int));
    m[2] = 3;
    printf("realloc后: %d, %d, %d\n", m[0], m[1], m[2]);


    // 4. free:释放内存
    free(m);
    free(c);
    return 0;
}
2. C++:new/deletenew[]/delete[]

C++ 在 C 的基础上提供了更安全的动态内存接口,支持对象的构造和析构:

  • new T**:**分配单个 T 类型对象的内存,自动调用构造函数初始化。
  • new T[n]**:**分配 n 个 T 类型对象的数组内存,自动调用每个对象的构造函数。
  • delete ptr**:**自动调用析构函数,释放单个对象内存。
  • delete[] ptr**:**自动调用每个对象的析构函数,释放数组内存。

【注意】:

  • 接口必须成对使用,new对应deletenew[]对应delete[],混用会导致内存泄漏或程序崩溃。
  • 无需手动类型转换,new返回对应类型指针,且分配失败时抛出bad_alloc异常(而非返回 NULL)。

【示例代码】:

代码语言:javascript
复制
int main()
{
    //内置类型
    int* a2 = new int;//不初始化
    delete a2;

    int* a3 = new int(1);//初始化
    delete a3;

    int* a4 = new int[4];//数组~不初始化
    delete[] a4;

    int* a5 = new int[4] {1, 2, 3, 4};//数组~初始化,不完全初始化的话,未初始化的都会被初始化为0
    delete[] a5;


    //自定义类型~用法和内置类型同理
    // new/delete 和 malloc/free最大区别是:
    // new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数


    return 0;
 
}
【面试题】:malloc/calloc/realloc的区别?

malloc 、 calloc 、 realloc 的区别主要在三点:

  • 参数不同: malloc 是单参数(总字节数), calloc 是双参数(元素个数+单个大小), realloc 是双参数(原地址+新字节数)。
  • 初始化不同: malloc 分配的内存未初始化, calloc 会自动初始化为0, realloc 的新增部分未初始化。
  • 功能不同: malloc 和 calloc 是首次分配内存, realloc 是调整已分配内存的大小。

【问题】:这里需要free(p2)吗?

代码语言:javascript
复制
void Test ()
{ 
    int* p2 = (int*)calloc(4, sizeof (int));
    int* p3 = (int*)realloc(p2, sizeof(int)*10);
    free(p3 );
    
}

不需要free(p2),无论是异地扩容还是原地扩容,最终都不需要专门释放p2,如果是原地扩容,p1和p2指向同一块空间,如果是异地的话,将原内容拷贝后会自动释放原来p2指向的空间

四、operator new/operator delete函数

new和delete 是用户进行 动态内存申请和释放的操作符operator new 和operator delete 是系统提供的 全局函数new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间

但是,追踪到底还是会调用malloc和free,因为毕竟c++兼容C语言,有现成开空间的函数直接调用不就行了呗

下面是operator new函数源码,malloc失败返回空,new失败抛异常(后面会学到)

【new底层汇编】

从图中可以看到是先调用operator new,再调用相对应的构造函数

【delete底层汇编】

delete的底层是先调用析构函数,然后再调用operator delete

【模拟栈对象如何在堆上申请/释放空间】

先为栈对象开空间,然后调用该对象的构造函数为_array开辟空间,最终让栈上的p1指针指向堆上的对象(栈对象和栈内存区域是两个不同领域的概念)

释放空间的话要先调用析构函数,如果你先调用operator delete了,_array就成野指针了,就会造成内存泄露

五、定位new表达式(特殊场景)

【定位new的用法】

定位 new 是一种 “在已分配的原始内存中调用构造函数” 的特殊用法,主要配合内存池使用(内存池分配的内存未初始化,需手动调用构造函数)

【池化技术】(简单了解)

这个图是在讲池化技术(以内存池为例)的工作逻辑,核心是解决“频繁内存申请/释放的效率问题”:

  • 传统方式的问题:像 Node* n1 = new Node(1) 这样直接用 new ,每次都要从堆里单独申请空间,频繁操作会导致效率低。
  • 内存池的作用:先提前在堆里申请一块“内存池”(整块空间);当需要新对象时,直接从内存池里分配小块空间(比如 pool.alloc(sizeof(Node)) 拿到空间),再用定位new( new(n2)Node(2) )在这块空间上构造对象。
  • 类比理解(左侧图):把“频繁零散的内存申请”比作“从小溪里一次次取水”,而“内存池”像“先把水存到池子里,需要时直接从池里取”,效率更高。

简单说就是:提前申请大块空间当“池”,后续从池里快速分配/复用空间,避免频繁直接操作堆。

六、常见面试题

1、malloc/free和new/delete的区别

malloc/free 和 new/delete 的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:undefined

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
2、什么是内存泄漏,内存泄漏的危害

什么是内存泄漏?

  • 内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害?

  • 长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
  • 普通程序,内存泄露影响不大,进程结束会释放资源;长期运行的程序,内存泄露危害很大,例如,游戏服务,电商服务……“最可怕的是那种每天只泄漏少量内存的程序 —— 像《王者荣耀》服务器,玩家每退出一次就漏一点背包数据的内存,初期玩家感觉不到卡顿,但连续运行几周后,累积的泄漏内存会把服务器撑满,最终突然崩溃,导致全服玩家掉线。”
3、如何避免内存泄漏

  • 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状 态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
  • 采用RAII思想或者智能指针来管理资源。
  • 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  • 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结一下:

内存泄漏非常常见,解决方案分为两种:

  • 1、事前预防型。如智能指针等。
  • 2、事后查错型。如泄漏检测工具。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、C/C++ 内存分布
    • 【C/C++中程序内存划分图】
    • 【相关题目】
  • 二、栈与堆对比
  • 三、动态内存管理
    • 1. C语言:malloc/calloc/realloc与free
    • 2. C++:new/delete与new[]/delete[]
    • 【面试题】:malloc/calloc/realloc的区别?
  • 四、operator new/operator delete函数
    • 【new底层汇编】
    • 【delete底层汇编】
    • 【模拟栈对象如何在堆上申请/释放空间】
  • 五、定位new表达式(特殊场景)
    • 【定位new的用法】
    • 【池化技术】(简单了解)
  • 六、常见面试题
    • 1、malloc/free和new/delete的区别
    • 2、什么是内存泄漏,内存泄漏的危害
    • 3、如何避免内存泄漏
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档