C++中的内存管理

一:程序内存

1.一个 C/C++ 程序的内存大致怎么分

程序内存可以分成了几个主要区域:栈、堆、数据段、代码段,其中,栈主要放非静态局部变量、函数参数等;堆负责运行时动态内存分配;数据段放全局和静态数据;代码段放可执行代码以及只读常量

2.指针变量在哪里,和它指向的东西在哪里

这是学内存管理必须真正想明白的一点。

例如:

cpp 复制代码
int* ptr1 = (int*)malloc(sizeof(int) * 4);

这里实际上存在两个东西:

cpp 复制代码
ptr1 本身
↓
一个局部指针变量

ptr1 指向的内存
↓
malloc 动态申请出来的一块空间

因为 ptr1 是 Test() 里的普通局部变量,所以:

复制代码
ptr1 本身 → 栈

但是:

复制代码
*ptr1 所在的动态内存 → 堆

同理:

cpp 复制代码
int* p = new int;

也是:

复制代码
p       → 如果是局部变量,通常在栈上
*p      → new 出来的空间在堆上

3.栈和堆的本质区别

栈上的内存通常随着函数调用自动管理。例如:

cpp 复制代码
void func()
{
    int a = 10;
}

a 是局部变量。

函数执行时它存在,函数结束之后,这部分局部对象的生命周期也随之结束。

而堆上的空间是程序运行过程中动态申请出来的,例如:

cpp 复制代码
int* p = new int;

或者:

cpp 复制代码
int* p = (int*)malloc(sizeof(int));

这块空间不会因为当前函数结束,就自动按照你写的动态内存接口帮你回收;所以强调动态申请的空间需要由自己配套释放

二:C 语言的动态内存管理:malloc / calloc / realloc / free

我们可以先简单回顾一下C 语言的动态内存管理:

cpp 复制代码
malloc(size)

负责申请 size 字节空间。

例如:

cpp 复制代码
int* p = (int*)malloc(sizeof(int) * 10);

意思是:申请能放10个int的空间

calloc 则常写成:

cpp 复制代码
int* p = (int*)calloc(10, sizeof(int));

表示申请:

cpp 复制代码
10 × sizeof(int)

大小的连续空间,并进行相应初始化处理。

realloc 是调整已有动态内存块的大小:

cpp 复制代码
p = (int*)realloc(p, sizeof(int) * 20);

而:

cpp 复制代码
free(p);

负责释放

三: C++ 的动态内存管理方式:new 和 delete

1.为什么 C++ 又发明了 new 和 delete?

C 的动态内存方式在 C++ 里仍然可以使用,但有些地方不够方便,因此 C++ 提供了自己的动态内存管理方式:new 和 delete

C++ 有"对象"

一个对象不仅仅需要:一块大小合适的内存

它还可能需要:构造函数,析构函数

2.new / delete 操作内置类型

拿以下代码举例:

cpp 复制代码
int* ptr4 = new int;

int* ptr5 = new int(10);

int* ptr6 = new int[3];

delete ptr4;
delete ptr5;
delete[] ptr6;

分别解释一下。

cpp 复制代码
int* ptr4 = new int;

表示:动态申请一个 int 空间

而:

cpp 复制代码
int* ptr5 = new int(10);

除了申请空间,还初始化:

cpp 复制代码
*ptr5 = 10

再看:

cpp 复制代码
int* ptr6 = new int[3];

这是一次创建三个连续的 int 空间。

所以释放的时候要注意:

复制代码
delete ptr4;
delete ptr5;
delete[] ptr6;

特别强调:

单个元素使用 new/delete,连续多个元素使用 new[]/delete[],必须匹配使用

这是必须记住的一条规则:

cpp 复制代码
new       ↔ delete

new[]     ↔ delete[]

不要写:

cpp 复制代码
int* p = new int[10];

delete p;        // 不应该这样配

应该:

cpp 复制代码
delete[] p;

3.new 自定义类型

以下定义了一个类:

cpp 复制代码
class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        cout << "A():" << this << endl;
    }

    ~A()
    {
        cout << "~A():" << this << endl;
    }

private:
    int _a;
};

然后比较:

cpp 复制代码
A* p1 = (A*)malloc(sizeof(A));

A* p2 = new A(1);

最大的区别就是:

cpp 复制代码
malloc
→ 只帮你得到一块内存

new
→ 得到内存
→ 还会调用构造函数

同样:

复制代码
free
→ 释放内存

delete
→ 先调用析构函数
→ 再释放内存

申请自定义类型时,new 会调用构造函数,delete 会调用析构函数,而 malloc/free 不会

4.区分:"有内存" ≠ "已经是对象"

假设:

cpp 复制代码
A* p = (A*)malloc(sizeof(A));

现在得到的是什么?

很多人可能会回答:一个 A 对象。

但是更准确地说只是:

一块足够容纳 A 的原始内存。

因为:

复制代码
A()

构造函数还没执行。

所以你可以这样理解:

复制代码
malloc(sizeof(A))

      ↓

┌─────────────┐
│ 一块原始空间 │
└─────────────┘

还没有经历 A 的构造过程

而:

复制代码
A* p = new A;

则是:

复制代码
申请空间
   ↓
调用 A()
   ↓
A 对象构造完成

这就是为什么 C++ 里的"对象生命周期"不能只用"内存有没有分配"来理解。

5.new 失败和 malloc 失败不同

cpp 复制代码
malloc失败
→ 返回 NULL

new失败
→ 抛异常

所以传统 C 写法可能是:

cpp 复制代码
int* p = (int*)malloc(100);

if (p == NULL)
{
    ...
}

而普通 C++ new 的错误处理机制不同

四:operator new与operator delete****函数

**1.**区分 new 和 operator new

假设有:

cpp 复制代码
class A
{
public:
    A(int x = 0)
        : _x(x)
    {
        cout << "A()" << endl;
    }

    ~A()
    {
        cout << "~A()" << endl;
    }

private:
    int _x;
};

我们写:

cpp 复制代码
A* p = new A(10);

很多人会把这句话理解成:

cpp 复制代码
申请 sizeof(A) 大小的内存

其实不完整。

对于自定义类型,new 原理是:

cpp 复制代码
new A(10)

        ↓

operator new(sizeof(A))
   申请原始内存

        ↓

调用 A 的构造函数
   在这块内存上构造对象

        ↓

返回 A*

自定义类型执行 new 时,会先调用 operator new 申请空间,再在申请出的空间上执行构造函数。

所以:

cpp 复制代码
new A(10)

可以从概念上理解成:

cpp 复制代码
void* ptr = operator new(sizeof(A));

// 在这块已经申请好的空间上构造 A 对象
A* p = new(ptr) A(10);

2.operator new 到底是什么?

operator new 是一个函数。

可以理解为:

cpp 复制代码
void* operator new(size_t size);

参数:

cpp 复制代码
size

表示:我要申请多少字节

返回:void*

表示:返回申请到的原始内存地址。

例如:

cpp 复制代码
void* p = operator new(100);

意思就是:给我申请 100 字节的原始内存。

但是注意:

cpp 复制代码
operator new(100);

并不知道你最终要在这里放:

复制代码
int
Date
A
String

中的哪一种对象。

它的核心任务就是:

负责空间,不负责对象构造。

所以 operator new 不会调用构造函数

例如:

cpp 复制代码
A* p = (A*)operator new(sizeof(A));

现在虽然 p 指向一块足够放下 A 的内存,但是:

cpp 复制代码
A()

还没有被调用,所以

cpp 复制代码
有了一块 sizeof(A) 的内存
≠
A 对象已经完成构造

如果想真正把 A 构造起来,可以继续:

cpp 复制代码
new(p) A(10);

于是:

cpp 复制代码
operator new(sizeof(A))
        ↓
只有原始空间

new(p) A(10)
        ↓
调用构造函数

        ↓
A 对象构造完成

3.operator delete 又是什么?

同理:

cpp 复制代码
operator delete

也是一个函数,主要负责:

释放原始内存。

可以理解成:

cpp 复制代码
void operator delete(void* p);

例如:

cpp 复制代码
operator delete(p);

负责释放 p 指向的空间。

但是:

operator delete 本身不是 delete 操作符,它的核心工作不是替你调用对象析构函数。

4.delete 和 operator delete 又是什么关系?

假设:

cpp 复制代码
A* p = new A(10);

最后:

cpp 复制代码
delete p;

delete 原理是:

cpp 复制代码
delete p

       ↓

 调用析构函数 ~A()
   清理对象内部资源

       ↓

调用 operator delete
   释放对象占据的原始空间

也就是说:

cpp 复制代码
delete p;

概念上可以拆成:

复制代码
p->~A();

operator delete(p);

delete是先执行析构函数完成资源清理,再调用 operator delete 释放空间。

相关推荐
Wang's Blog6 小时前
Java 项目实战: 交友项目:登录链路回顾与今日任务地图
java·开发语言·状态模式·交友
砚底藏山河6 小时前
python量化:行情数据回溯
java·开发语言·python·金融·maven
今年下半年6 小时前
AI智能体项目实战记录(02):初始化 Python 项目
开发语言·人工智能·python·fastapi
Williamtym7 小时前
PickGo Lab实践:用浏览器、Python和MCP串起机器人搬运仿真
开发语言·python·机器人
DongQiShanRen7 小时前
六根硬件映射(四):归属澄清、两处不一致与卷十一总表
开发语言·人工智能·算法·rust·哈希算法·集成学习
CS创新实验室7 小时前
Python 对象“死“了之后去了哪?——引用计数与分代垃圾回收
开发语言·jvm·python
quantdash_cc7 小时前
Pandas 处理分钟级股票行情:从原始数据清洗到 K 线聚合
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
yu_zhidun7 小时前
企业电脑监控软件怎么选:别只看功能清单,先看这 6 个底层能力
开发语言·电脑·php·域智盾系统
把我格子衫拿来7 小时前
VTK 9.5 与 Qt 6 联合开发:从嵌入三维窗口到可维护的桌面架构
c++·python·qt·vtk·三维可视化
无忧.芙桃8 小时前
C++语言原理与实践(十六):多态机制讲解
开发语言·c++·多态