C++(内存管理)

👋 欢迎阅读

🎯 欢迎来到「C++ 内存管理」深入讲解! 本文将带你从"程序运行时内存究竟如何划分"这一基础问题出发,逐步深入理解 C++ 内存分布、C 语言动态内存管理方式、C++ 内存管理方式、malloc/free 与 new/delete 的区别,以及内存泄漏的成因与防范。

在开始之前,建议你先:

  • 了解背景知识:内存管理是 C/C++ 编程中至关重要的一环。不同于 Java、Python 等带有自动垃圾回收的语言,C++ 将内存管理的责任交给了程序员。理解内存的布局和动态分配方式,是写出健壮、高效、无内存泄漏代码的基础。

  • 明确学习目标 :掌握以下五个核心知识点------

    C++ 内存分布 :理解 C++ 程序运行时内存的五大区域------ (局部变量、函数调用帧)、 (动态分配的内存)、数据段/静态区 (全局变量、静态变量)、常量区 (字符串常量、只读数据)以及代码段 (可执行指令),并了解各区域的特点与生命周期;

    C 语言动态内存管理方式 :掌握 C 语言中 malloccallocreallocfree 四个函数的用法、参数含义及返回值处理,理解它们各自的应用场景(如 calloc 会初始化零、realloc 可调整已有内存大小);

    C++ 内存管理方式 :掌握 C++ 中 newdelete 的用法,理解 new自动计算大小、调用构造函数delete调用析构函数 ,并熟悉 new[]delete[] 在数组分配中的配对使用;

    malloc/free 和 new/delete 的区别 :从多个维度深入对比两者的差异------new/delete运算符 (可重载),malloc/free函数new 会调用构造/析构函数,而 malloc/free 不会;new 失败时抛异常,malloc 失败时返回 NULL;以及它们底层实际分配内存的机制异同;

    内存泄漏 :理解内存泄漏的定义------已分配的内存不再使用且无法被释放

一.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);
}
//1. 选择题:
//选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
//globalVar在哪里?____ staticGlobalVar在哪里?____
//staticVar在哪里?____ localVar在哪里?____
//num1 在哪里?____
//char2在哪里?____ *char2在哪里?___
//pChar3在哪里?____ *pChar3在哪里?____
//ptr1在哪里?____ *ptr1在哪里?____

说明

  1. 又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。

  2. 内存映射段先不作了解后面会讲到。

  3. 用于程序运行时动态内存分配,堆是可以上增长的。

  4. 数据段--存储全局数据和静态数据。

  5. 代码段--可执行的代码/只读常量。

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

cpp 复制代码
void Test ()
{
    int* p1 = (int*) malloc(sizeof(int));
    free(p1);
    
    // 1.malloc/calloc/realloc的区别是什么?
    int* p2 = (int*)calloc(4, sizeof (int));
    int* p3 = (int*)realloc(p2, sizeof(int)*10);
    
    // 这里需要free(p2)吗?
    free(p3 );
}

说明

  1. **malloc:**只管分配内存,不管内容;

  2. **calloc:**在分配内存的同时自动清零

  3. **realloc:**负责调整已分配内存的大小

  4. **free:**用于释放空间

因此可以回答上面那个问题,不需要free,因为realloc相当于接手了p2指向的内存,你p2释放也是释放那个区域,你p3释放也是那个区域,因此不需要也不能否则就重复释放导致崩溃!

三.C++ 内存管理方式

3.1 new/delete操作内置类型

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过 new delete 操作符进行动态内存管理

格式:类型指针 变量 = new 类型对象个数{初始化}

格式:类型指针 变量 = new 类型(初始化)

cpp 复制代码
void Test()
{
	// 动态申请一个int类型的空间
   int* ptr4 = new int;

   // 动态申请一个int类型的空间并初始化为10
   int* ptr5 = new int(10);

   // 动态申请10个int类型的空间
   int* ptr6 = new int[10];

   delete ptr4;
   delete ptr5;
   delete[] ptr6;
}

注意:申请和释放单个元素的空间,使用 new delete 操作符,申请和释放连续的空间,使用 new\[\] delete\[\],注意:匹配起来使用。

3.2 new/delete操作自定义类型

cpp 复制代码
class A
{
public:
    A(int a = 0)
    : _a(a)
    {
        cout << "A():" << this << endl;
    }
    ~A()	
    {
            cout << "~A():" << this << endl;
    }
private:
	int _a;
};
int main()
{
	//最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
	A* p1 = (A*)malloc(sizeof(A));
    A* p2 = new A(1);
    free(p1);
    delete p2;

    // 内置类型是几乎是一样的
    int* p3 = (int*)malloc(sizeof(int));
    int* p4 = new int;
    free(p3);
    delete p4;

    A* p5 = (A*)malloc(sizeof(A)*10);
    A* p6 = new A[10];
    
    free(p5);
    delete[] p6;
	return 0	
}

注意:在申请自定义类型的空间时, new 会调用构造函数, delete 会调用析构函数,而 malloc free 不会。其实这也很好理解因为malloc和free出来的时候还没有类这个概念

3.3 operator new与operator delete函数

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

而在其中operator new 实际也是通过malloc来申请空间 ,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete最终是通过free来释放空间的

3.4 new和delete的实现原理

内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似。

自定义类型

  • new的原理

    1. 调用operator new函数申请空间

    2. 在申请的空间上执行构造函数,完成对象的构造

  • delete的原理

    1. 在空间上执行析构函数,完成对象中资源的清理工作

    2. 调用operator delete函数释放对象的空间

  • new N的原理

    1. 调用operator new\[\]函数,在operator new\[\]中实际调用operator new函数完成N个对象空间的申请

    2. 在申请的空间上执行N次构造函数

  • delete的原理\[\]

    1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理

    2. 调用operator delete\[\]释放空间,实际在operator delete\[\]中调用operator delete来释放空间

3.5 搭配不当

自定义类型:如果new和delete对应不当,程序会崩溃

内置类型:如果new和delete对应不当,程序不会崩溃但不建议使用

四.malloc/free和new/delete的区别(常见面试题)

共同点:都是从堆上申请空间,并且需要用户手动释放

不同点:

  1. malloc和free是函数 ,new和delete是操作符

  2. malloc申请的空间不会初始化 ,new会初始化

  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型可, 如果是多个对象,\[\]中指定对象个数即可

  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型

  5. malloc申请空间失败时,返回的是NULL,new申请空间失败时,报异常。(因为前者是函数,后者是操作符)

  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数 ,而new在申请空间后会调用构造函数完成对象的初始化 ,delete在释放空间前会调用析构函数完成空间中资源的清理

五.内存泄露

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

内存泄漏危害:内存泄漏会导致响应越来越慢,最终卡死。

cpp 复制代码
void MemoryLeaks()
{
    // 1.内存申请了忘记释放
    int* p1 = (int*)malloc(sizeof(int));
    int* p2 = new int;

    // 2.异常安全问题
    int* p3 = new int[10];
    Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
    delete[] p3;
}

C/C++程序中一般我们关心两种方面的内存泄漏:

  1. 堆内存泄漏

  2. 系统资源泄漏

🎯 闭幕

🎉 恭喜你完成了 C++ 内存管理核心知识的学习!今天我们系统梳理了 C++ 内存分布C语言动态内存管理方式C++ 内存管理方式malloc/free 和 new/delete 的区别 以及 内存泄漏 这五大核心模块。

🚀 动手实践

在本地 IDE 或在线编译器中编写代码,尝试不同的测试用例,亲身体验每个知识点的用法。

💡 深入思考

  • C++ 内存分布 :C++ 中 栈、堆、数据段(静态区)、代码段 分别存储什么类型的数据?全局变量、静态变量、局部变量、常量字符串 各存储在哪个区域?

  • C语言动态内存管理方式malloccallocrealloc区别 是什么?free 如何知道要释放多大的内存?

  • C++ 内存管理方式newdelete执行流程 是什么?new[]delete[] 为什么必须配对使用?

  • malloc/free 和 new/delete 的区别本质区别 是什么?能否混用 (如 malloc 分配 delete 释放)?为什么?

  • 内存泄漏什么是内存泄漏内存泄漏的危害 是什么?

📚 延伸挑战

  • 分析以下代码中变量各存储在哪个内存区域:int a; static int b; const char* p = "hello"; char arr[10]; int* heap = new int(10);

  • 分别使用 malloc/freenew/delete 实现 动态数组的创建和释放,对比语法差异。

  • 定义一个 Student 类(包含 nameage),分别用 new 创建单个对象和对象数组,并用 delete 正确释放。

  • 分析以下代码的问题并修复:int* p = new int[10]; // ... delete p;

如果你觉得本文对你有所帮助,欢迎:

👍 点赞 / 收藏

👤 关注作者,获取更多题解

💬 留言交流你的疑问或优化思路


🔍 深入思考参考答案

  • C++ 内存分布 存储局部变量、函数参数、返回值等; 存储动态分配的内存(malloc/new);数据段(静态区) 存储全局变量和静态变量(未初始化的在 BSS 段);代码段 存储可执行代码和常量(如常量字符串)。const 修饰的局部变量在栈上,const 修饰的全局变量在数据段的只读区。

  • C语言动态内存管理方式malloc 分配指定字节数的内存(不初始化),calloc 分配并初始化为 0,realloc 调整已分配内存的大小。free 释放时,内存块头部记录了大小信息(由 malloc 实现维护)。

  • C++ 内存管理方式new 先调用 operator new 分配内存,再调用构造函数;delete 先调用析构函数,再调用 operator delete 释放内存。new[]delete[] 必须配对,否则会导致未定义行为(可能只析构第一个对象)。

  • malloc/free 和 new/delete 的区别malloc/free函数new/delete运算符new 会调用构造函数,delete 会调用析构函数,而 malloc/free 不会。不能混用 ,因为底层实现机制不同(如 new 可能重载,内存布局不同)。

  • 内存泄漏 :内存泄漏指 动态分配的内存未被释放,导致内存占用持续增加,最终可能耗尽系统内存。

🛠️ 延伸挑战参考解法

1. 内存分布分析

cpp 复制代码
/*
 * 功能:分析 C++ 程序中不同变量的内存分布
 * 重点:栈、堆、静态区、代码段各存储什么类型的数据
 */

#include <iostream>
using namespace std;

// 全局变量(未初始化)→ 数据段(BSS 段)
int g_global;

// 全局变量(已初始化)→ 数据段(数据区)
int g_init = 100;

// 全局常量 → 数据段(只读区)
const int g_const = 200;

// 静态全局变量 → 数据段(BSS 段)
static int g_static;

void TestMemoryDistribution() 
{
    // 局部变量 → 栈
    int local_var;
    static int local_static = 50;   // 静态局部变量 → 数据段(数据区)
    const int local_const = 30;     // const 局部变量 → 栈(不是常量区!)
    
    // 常量字符串 → 代码段(只读常量区)
    const char* pStr = "hello world";
    
    // 指针变量本身在栈上,指向的内存取决于分配方式
    char arr[10] = "hello";         // arr 数组本身在栈上
    
    // 动态分配的内存 → 堆
    int* pHeap = new int(10);
    delete pHeap;
}

int main() 
{
    cout << "=== 内存分布总结 ===" << endl;
    cout << "栈:局部变量、函数参数、返回值" << endl;
    cout << "堆:动态分配的内存(malloc/new)" << endl;
    cout << "数据段(静态区):全局变量、静态变量" << endl;
    cout << "代码段:可执行代码、常量字符串、const 全局变量" << endl;
    cout << "\n注意:const 局部变量在栈上,不在常量区!" << endl;
    
    TestMemoryDistribution();
    return 0;
}

2. malloc/free vs new/delete 动态数组

cpp 复制代码
/*
 * 功能:对比 malloc/free 和 new/delete 的语法差异
 * 场景:动态数组的创建和释放
 * 重点:new/delete 是运算符,malloc/free 是函数
 *      new 会调用构造函数,delete 会调用析构函数
 */

#include <iostream>
#include <cstdlib>   // malloc/free
using namespace std;

class Test 
{
private:
    int _val;

public:
    Test(int val = 0) 
        : _val(val) 
    {
        cout << "Test() constructed, val=" << _val << endl;
    }

    ~Test() 
    {
        cout << "~Test() destructed, val=" << _val << endl;
    }

    void Print() const 
    {
        cout << "Test val: " << _val << endl;
    }
};

int main() 
{
    cout << "=== malloc/free 分配(不调用构造/析构) ===" << endl;
    // malloc 只分配内存,不调用构造函数
    Test* p1 = (Test*)malloc(sizeof(Test) * 3);
    // p1 指向的内存未初始化,调用 Print 可能出错
    // p1[0].Print();   // 未定义行为!
    free(p1);   // free 只释放内存,不调用析构函数

    cout << "\n=== new/delete 分配(调用构造/析构) ===" << endl;
    // new 分配内存并调用构造函数
    Test* p2 = new Test(100);
    p2->Print();   // 对象已构造,安全调用
    delete p2;     // 先调用析构函数,再释放内存

    cout << "\n=== new[]/delete[] 分配数组 ===" << endl;
    // new[] 分配数组,每个元素都调用构造函数
    Test* p3 = new Test[3]{10, 20, 30};
    for (int i = 0; i < 3; i++) 
    {
        p3[i].Print();
    }
    delete[] p3;   // 每个元素都调用析构函数

    return 0;
}

3. new 创建对象和对象数组

cpp 复制代码
/*
 * 功能:演示 new/delete 创建和释放单个对象及对象数组
 * 重点:单个对象用 new/delete,数组用 new[]/delete[]
 *      必须配对使用,否则导致未定义行为
 */

#include <iostream>
#include <cstring>
using namespace std;

class Student 
{
private:
    char* _name;   // 动态分配的姓名
    int _age;

public:
    // 构造函数:为姓名分配堆内存
    Student(const char* name, int age) 
        : _age(age) 
    {
        _name = new char[strlen(name) + 1];
        strcpy(_name, name);
        cout << "Student \"" << _name << "\" constructed" << endl;
    }

    // 析构函数:释放姓名占用的堆内存
    ~Student() 
    {
        cout << "Student \"" << _name << "\" destructed" << endl;
        delete[] _name;
        _name = nullptr;
    }

    void Print() const 
    {
        cout << "Name: " << _name << ", Age: " << _age << endl;
    }
};

int main() 
{
    cout << "=== 创建单个对象 ===" << endl;
    // new 分配内存 + 构造
    Student* s1 = new Student("Alice", 20);
    s1->Print();
    // delete 析构 + 释放内存
    delete s1;

    cout << "\n=== 创建对象数组 ===" << endl;
    // new[] 分配内存 + 逐个构造
    Student* sArr = new Student[3] 
    {
        {"Bob", 21},
        {"Charlie", 22},
        {"David", 23}
    };
    
    for (int i = 0; i < 3; i++) 
    {
        sArr[i].Print();
    }
    
    // delete[] 逐个析构 + 释放内存
    delete[] sArr;

    return 0;
}

4. new\[\] 和 delete 混用的错误分析

cpp 复制代码
/*
 * 功能:分析 new[] 和 delete 混用的错误
 * 场景:new[] 分配数组,但使用 delete(而非 delete[])释放
 * 重点:new[] 必须配 delete[],否则导致未定义行为
 *      对于内置类型(如 int),可能不明显,但仍是错误用法
 */

#include <iostream>
using namespace std;

class Test 
{
private:
    int _val;

public:
    Test(int val = 0) 
        : _val(val) 
    {
        cout << "Test(" << _val << ") constructed" << endl;
    }

    ~Test() 
    {
        cout << "~Test() destructed, val=" << _val << endl;
    }
};

int main() 
{
    cout << "=== 正确用法:new[] + delete[] ===" << endl;
    Test* p1 = new Test[3]{1, 2, 3};
    delete[] p1;   // 正确:每个对象都调用析构函数
    cout << endl;

    cout << "=== 错误用法:new[] + delete(未定义行为) ===" << endl;
    Test* p2 = new Test[3]{4, 5, 6};
    // delete p2;   // 错误!应该用 delete[]
    // 使用 delete 替代 delete[] 会导致:
    // 1. 只调用第一个对象的析构函数(其余未析构)
    // 2. 内存释放时可能出错(数组内存布局不同)
    // 取消注释并运行可能崩溃或行为异常
    cout << "Warning: delete instead of delete[] is undefined behavior!" << endl;
    // 为了演示正确性,这里使用正确的 delete[]
    delete[] p2;

    cout << "\n=== 内置类型也不应混用 ===" << endl;
    int* p3 = new int[10];
    // delete p3;   // 虽然对内置类型可能不会崩溃,但仍是未定义行为
    delete[] p3;   // 正确用法

    return 0;
}

祝你在 C++ 的学习之路上越走越稳,早日掌握更多编程技巧!下次见 🚀✨

相关推荐
皓月斯语1 小时前
P2858 [USACO06FEB] Treats for the Cows G/S
数据结构·c++·算法·动态规划
无敌贵点大王1 小时前
RTThread学习记录8——定时器tick相关,解密delay原理
学习
旖旎夜光1 小时前
LeetCode 397:整数替换(贪心问题) —— 题解
数据结构·c++·算法·leetcode·贪心算法
≮傷£≯√1 小时前
QT配置FFmpeg
开发语言·qt·ffmpeg
XH华1 小时前
Linux系统第四章:工具的学习
linux·运维·学习
别动我齐刘海1 小时前
“三层同步审计”判定掉帧缺失
c语言·c++·人工智能·深度学习·学习·机器学习·机器人
三8441 小时前
RCE长度&字符限制绕过
开发语言·php
小陈的进阶之路2 小时前
爬虫三大解析库:lxml, jsonpath, bs4
开发语言·笔记·python
fanged2 小时前
设备树学习5--读写实操(TODO)
学习