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 释放空间。

相关推荐
上海广测检测科技有限公司4 小时前
蓝牙耳机俄罗斯EAC认证详解:欧亚经济联盟体系下TWS与头戴式的合规路径
经验分享
jingli94 小时前
本地向量库索引把 C 盘塞爆 48.6G:一次 LanceDB 膨胀排查与根治实录
c语言·开发语言
言乐64 小时前
Python排名统计折线图
开发语言·python·django·virtualenv·pygame
浩瀚地学4 小时前
deepagents学习打卡day08
经验分享·笔记·python·学习·agent
焱焱枫4 小时前
OceanBase 为什么要多一层 Unit 和 ResourcePool?Tenant 直接分 CPU 内存不行吗?多租户资源模型一次讲透
经验分享
艾莉丝努力练剑4 小时前
【AI大模型接入SDK】ChatSDK整体实现
网络·c++·人工智能·学习·架构
我是小白呀4 小时前
19-Temporal项目实战:将客户开通流程迁移到持久执行架构
java·开发语言·人工智能·架构·workflow
励志不掉头发的内向程序员4 小时前
【从零写一个CAD 02】画完第一条线之后:实体容器、Esc 取消,和按了没反应的键盘
开发语言·c++·qt·学习·系统架构
LCDduhui4 小时前
LCD屏幕中常见的液晶屏驱动芯片有哪些
经验分享