C++修炼:内存管理

news/2025/3/22 18:34:42/

        Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!

我的博客:<但凡.

我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C++修炼之路》

欢迎点赞,关注!

目录

 

1、 C语言内存管理复习

2、C++内存管理方式

3、operator new和operator delete函数(重点)

4、new和delete底层剖析

5、定位new表达式

6、new和malloc,delete和free的区别


 

1、 C语言内存管理复习

        首先我们来先看一道题,复习一下C语言的内存管理:

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 在哪里?____    

        char2在哪里?____     *char2在哪里?___   pChar3在哪里?____        

        *pChar3在哪里?____   ptr1在哪里?____           *ptr1在哪里?____

         我们依次来看:globalVar在主函数外定义,自然是一个全局变量,全局变量是存储在静态区的。

staticGlobalVar是一个static修饰的静态变量,同时也是全局变量,自然也是在静态区的。

staticVar是一个static修饰的静态变量,也是存储在静态区的。

localVar是一个普通的变量,他是定义在函数里的所以存储在栈区。

num1也是定义在函数内部也是存放在栈区的。

char2没什么好说的也是存放在栈区和num1一样。

*char2应该是字符'a’,他也是定义在函数中的所以存放在栈区。

pChar3*pChar3我们一起看,其实pChar3是存放在栈区的,因为它是定义在函数中的,但是*pChar3并不是,因为他代表的是一个常量字符,应该存放在常量区。

ptr1*ptr1一起来看,和上面的同理,ptr1也是存放在栈区的,但是这个prt1指向的内容是malloc开辟出来的,动态开辟出来的空间都是存放在堆区的。

答案:

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

        globalVar在哪里?_C___     staticGlobalVar在哪里?__C__  

        staticVar在哪里?__C__     localVar在哪里?_A___   num1 在哪里?_A___    

        char2在哪里?__A__     *char2在哪里?_A__   pChar3在哪里?_A___        

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

2、C++内存管理方式

         在C语言中我们是通过malloc,calloc和realloc来开辟空间的,用free来释放空间。

而在C++中,我们拥有的新的方式来开辟和释放空间,那就是new和delete。

我先写一串代码让大家看看是怎么使用的。

#include<iostream>
using namespace std;
int main()
{int* ptr1 = new int;//开辟空间int* ptr2 = new int(5);//开辟空间并复制cout << *ptr2 << endl;int* ptr3 = new int[3];//开辟多个空间*ptr3 = 9;*(ptr3 + 1) = 10;*(ptr3 + 2) = 11;cout << ptr3[0]<<" " << ptr3[1]<<" " << ptr3[2]<<" " << endl;//释放空间delete ptr1;delete ptr2;delete[] ptr3;return 0;
}

  输出结果:

        以上代码就是我们正常使用new和delete的代码。需要注意一点我们的new和delete是对应使用的,什么意思?就拿上面我们的代码来说我们的ptr3new出来的3个int空间,我们是放的时候应该把这3个空间都释放掉,所以得用delete[]连续释放空间。

        看到这里其实new和delete根C语言里面的内存管理函数也没什么区别吗,但是其实new厉害的地方就在于他可以更加便利的对自定义类型进行操作:

#include<iostream>
using namespace std;
class A
{
public:A(int b){a = b;}~A(){cout << "~A()" << endl;}
private:int a;
};
int main()
{A* p = new A(1);delete p;return 0;
}

        比如这样,我们在开辟空间的时候就可以对自定义类型的数据进行赋值。这是malloc不能够做到的。但对于内置类型的数据其实new和malloc几乎没什么区别。

3、operator new和operator delete函数(重点)

        new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数。

        new在开辟空间的时候实际上会调用operator new,而delete在释放空间的时候会调用operator delete。

        其实咱们对new和delete底层剖析一下,他们也是调用的malloc和free。

        看到这大家可能会有两个问题,第一,malloc,free和operator new,operator delete有什么联系?第二,C++在设计之初为什么要让new和delete来调用malloc和free呢?带着这两个问题我们继续往下看。

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid *p;while ((p = malloc(size)) == 0)if (_callnewh(size) == 0){// report no memory// 如果申请内存失败了,这里会抛出bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);
}

        这是operator new的定义,我们可以看到他调用了malloc,但是他其实是对malloc还做了一些其他的封装,这不是我们了解的重点。operator delete也是这样,这里代码就不放出来了。

        那么现在看第二个问题,为什么要调用malloc和free呢?

        首先我们要知道malloc和new在开辟空间失败时报错的方式是不同的。malloc是直接返回一个空指针,导致这个空间无法被使用。而new是抛异常,那问题又来了,什么是抛异常呢?

        在C++中,异常处理是一种在程序运行时处理错误的机制。它使用trycatchthrow关键字来实现。当程序中出现错误时,可以使用throw关键字抛出一个异常,然后在try块中捕获并使用catch块进行处理。

        我们可以这么理解,异常是一种比较温柔的处理方式。在malloc开辟空间失败时会运行报错,但是异常他是不会显示出来的。 我们必须自己去捕获才知道到底是什么错误。当然了这不是重点,我们未来还会继续说这一点。

        由于malloc和operator new的报错方式不一样,所以我们的operator new对malloc进行了一层封装,让他开辟失败时抛异常而不是返回空指针。

4、new和delete底层剖析

        在实际new和delete自定义类型的时候,调用的顺序其实是operator new->构造函数->...处理数据...->析构函数->operator delete。

        首先,new[]会在开辟内存的时候多开辟4个空间(32位),其实这4个空间存放的是一个int类型的值,这个值是表示我们连续开辟了几个该自定义类型的空间。

        其实这个额外的四个字节存放的数不是给我们new使用的,他是给delete[]使用的。我们前面提到过,delete会调用析构函数,我们delete[]连续调用几个析构函数呢?delete[]是不知道的。所以就诞生了这么一个值来指示我们的delete[]应该调用几个析构函数

        但其实需要注意一点,如果我们不写这个析构函数的话,这时候释放空间的时候我们的析构函数就不知道调用几次,这时候要么报错,要么就单纯的没释放完,因为只调用了一次析构函数。

        有意思的是如果我们不写析构函数,连续开辟空间,但是不用delete[]而是用delete那就没有问题,因为delete最后也是调用free,free就干脆全都释放掉了。虽然可行但是我们还是得保证使用的类型对应着,[]开辟就[]释放,不要为自己的代码埋下地雷。

5、定位new表达式

        这个其实就是我们可以实现在已分配的原始内存空间中调用构造函数初始化一个对象。因为其实我们是没办法直接调用构造函数的。

//等价于new
A* p1 = (A*)operator new(sizeof(A));
//构造函数不支持显示调用
//p1->A();
//定位new/replacement new 显示调用构造函数
new(p1)A(10);//等价于delete
p1->~A();//析构函数可以显示调用
operator delete(p1);
return 0;

        这个只做了解就好。

6、new和malloc,delete和free的区别

        共同点:都是从堆上申请空间,并且手动维护

        不同点:

        1、malloc和free是函数,而new和delete是操作符。

        2、malloc申请空间不能初始化而new可以初始化。

        3、malloc需要手动计算字节多少,而new只需要传空间类型就好。

        4、malloc返回值为void*,使用时必须强制转换,new不需要

        5、malloc申请空间失败返回NULL,而new是抛异常。

        6、malloc和free不会调用构造和析构函数。

        好了。今天的内容就分享到这,我们下期再见!

 


http://www.ppmy.cn/news/1581191.html

相关文章

从零开始学可靠消息投递:分布式事务的“最终一致性”方案

一、什么是可靠消息投递&#xff1f;—— 消息队列的“防丢宝典” 可靠消息投递 是指通过消息队列&#xff08;如 RocketMQ&#xff09;确保消息在生产、传输、消费过程中不丢失、不重复、有序到达。其核心目标是在分布式系统中保障数据最终一致性&#xff0c;常用于订单处理、…

DeepSeek-R1思路训练多模态大模型-Vision-R1开源及实现方法思路

刚开始琢磨使用DeepSeek-R1风格训练多模态R1模型&#xff0c;就看到这个工作&#xff0c;本文一起看看&#xff0c;供参考。 先提出问题&#xff0c;仅靠 RL 是否足以激励 MLLM 的推理能力&#xff1f; 结论&#xff1a;不能&#xff0c;因为如果 RL 能有效激励推理能力&#…

Jetpack组件在MVVM架构中的应用

Jetpack组件在MVVM架构中的应用 一、引言 Jetpack是Android官方推出的一套开发组件工具集,它能够帮助开发者构建高质量、可维护的Android应用。本文将深入探讨Jetpack核心组件在MVVM架构中的应用。 二、ViewModel组件 2.1 ViewModel基本原理 ViewModel是MVVM架构中最重要…

分布式系统中分布式ID生成方案的技术详解

分布式系统中分布式ID生成方案的技术详解 一、分布式系统唯一ID的特点二、分布式系统唯一ID的实现方案1. UUID2. 数据库生成ID3. Redis生成ID4. Snowflake雪花算法5. 美团Leaf 三、总结 在复杂的分布式系统中&#xff0c;数据被分散存储在不同的节点上&#xff0c;每个节点都有…

【零基础入门unity游戏开发——unity3D篇】3D模型 —— Rig操纵页签和Avatar化身系统

参考原文:https://blog.csdn.net/linxinfa/article/details/116666936 考虑到每个人基础可能不一样,且并不是所有人都有同时做2D、3D开发的需求,所以我把 【零基础入门unity游戏开发】 分为成了C#篇、unity通用篇、unity3D篇、unity2D篇。 【C#篇】:主要讲解C#的基础语法,…

Python第六章07:元组的定义和操作

# tuple元组的定义和操作# tuple元组定义用小括号&#xff1a;(1,2,3,4,5),可以是不同类型元素 # 给变量定义元组时&#xff0c;写括号不写tuple&#xff1a; a (1,2,3,4,5) # 变量 &#xff08;&#xff09; 变量 tuple&#xff08;&#xff09; 空元组变量 # tuple…

C#零基础入门篇(18. 文件操作指南)

## 一、文件操作基础 在C#中&#xff0c;文件操作主要通过System.IO命名空间中的类来实现&#xff0c;例如File、FileStream、FileInfo等。 ## 二、常用文件操作方法 ### &#xff08;一&#xff09;文件读取 1. **使用File.ReadAllText方法读取文件内容为字符串** …

Linux系统中安装各种常用中间件

Linux安装docker 安装docker 定制软件源 yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 安装最新版docker yum list docker-ce --showduplicates | sort -r…