C++新经典 | C++ 查漏补缺(内存)

news2025/7/19 21:02:59

目录

 一、new和delete

1.new类对象时,括号问题

2.new做了什么事

3.delete做了什么事

4.new与malloc的区别

5.delete与free的区别

二、分配及释放内存

三、重载operator new和operator delete操作符

1.重载类中的operator new和operator delete操作符

(1)为数组分配内存

2.重载全局operator new和operator delete操作符

四、内存池

1.内存池要解决的主要问题是什么?

2.内存池的实现原理是什么呢?

五、定位new


一、new和delete

        一般来讲,写C++程序,多数情况下还是提倡使用new和delete,不提倡使用malloc和free(这是C编程风格中才使用的)。

1.new类对象时,括号问题

(1)如果是一个空类,加不加括号没什么区别。

(2)类中如果有成员变量,带括号这种初始化对象的方式会把一些和成员变量有关的内存内容设置为0(内存中显示的内容是0)。

(3)如果类中有构造函数,main中的这两行代码执行的结果又变得相同了,如果构造函数中没有给age初始化,那么最终age的值没有被初始化为0,而是一个随机的值。

class TestMemory {
public:
    int age;
};

void main()
{
    TestMemory *p = new TestMemory();
    TestMemory *p1 = new TestMemory;
    std::cout << p->age << std::endl;      //0
    std::cout << p1->age << std::endl;     //-842150451
}

2.new做了什么事

        new关键字主要做了两件事:①一个是调用operator new;②一个是调用类的构造函数。(调试中可以使用F11键(或选择“调试”→“逐语句”命令)跳转进operator new,发现operator new调用了malloc)new关键字的调用关系如下表示:

    TestMemory *p = new TestMemory();
        operator new
            malloc
        TestMemory::TestMemory();

3.delete做了什么事

        delete关键字释放内存时的大概调用关系(注意调用顺序)如下:

    delete p;
        TestMemory::~TestMemory();//如果有析构函数,先调用析构函数
        operator delete();
            free(); 

4.new与malloc的区别

(1)new是关键字/操作符,而malloc是函数。

(2)new一个对象的时候,不但分配内存,而且还会调用类的构造函数(当然如果类没有构造函数,系统也没有给类生成构造函数,那没法调用构造函数了)。

(3)另外刚才也看到了,在某些情况下,“A *pa=newA();”可以把对象的某些成员变量(如m_i)设置为0,这是new的能力之一,malloc没这个能力。

(4)new最终是通过调用malloc来分配内存的。

5.delete与free的区别

        delete不但释放内存,而且在释放内存之前会调用类的析构函数(当然必须要类的析构函数存在)。

二、分配及释放内存

        分配内存这件事,假设分配出去的是10字节,但这绝不意味着只是简单分配出去10字节(而是比10字节多很多),而是在这10字节周围的内存中记录了很多其他内容,如记录分配出去的字节数等。(分配内存时为了记录和管理分配出去的内存,额外多分配了不少内存,造成了浪费,尤其是对于频繁分配小块的内存,浪费就显得更加严重)。

        释放一块内存,影响的范围很广,虽然分配内存的时候分配出去的是10字节,但释放内存的时候影响的远远不止是10字节的内存单元,而是一大片。(free一个内存块并不是一件很简单的事,free内部有很多的处理,包括合并临近空闲内存块、登记空闲块的大小、设置空闲块首位的一些标记以方便下次分配等一系列工作。)

    char* point = new char[10];

        总之,编译器要有效地管理内存的分配和回收,肯定在分配一块内存之外额外要多分配出许多空间保存更多的信息。编译器最终是把它分出去的这一大块内存中间某个位置的指针返回给point,作为程序员能够使用的内存的起始地址。也就是说,程序员拿到的point的地址实际上是malloc所分配出去的地址中中间的某个地址。

三、重载operator new和operator delete操作符

1.重载类中的operator new和operator delete操作符

        如果不想用自己写的operator new和operator delete成员函数了,怎样做到呢?当然不需要把类中的operator new和operator delete注释掉,只需要在使用new和delete关键字时在其之前增加“::”(两个冒号)即可。两个冒号叫作“作用域运算符”,在new和delete关键字之前增加“::”的写法,表示调用全局的new和delete关键字。此时,就不会调用类中的operator new和operator delete了。

class TestOverrideOperator {
public:
    static void* operator new(size_t size) //重载时第一个参数必须时size_t类型
    {
        std::cout << "调用重载operator new" << std::endl;
        TestOverrideOperator* mem = (TestOverrideOperator*)std::malloc(size);
        return mem;
    }
    static void operator delete(void* p)  // 重载时第一个参数必须时void*类型
    {
        std::cout << "调用重载operator delete" << std::endl;
        free(p);
    }
    static void* operator new[](size_t size) //重载时第一个参数必须时size_t类型
    {
        std::cout << "调用重载operator new[]" << std::endl;
        TestOverrideOperator* mem = (TestOverrideOperator*)std::malloc(size);
        return mem;
    }
    static void operator delete[](void* p)  // 重载时第一个参数必须时void*类型
    {
        std::cout << "调用重载operator delete[]" << std::endl;
        free(p);
    }
    TestOverrideOperator()
    {
        std::cout << "调用构造函数" << std::endl;
    }
    ~TestOverrideOperator()
    {
        std::cout << "调用析构函数" << std::endl;
    }
};
void TestNewAndDelete()
{
    std::cout << "-------调用重载operator new和重载operator delete------" << std::endl;
    TestOverrideOperator *test = new TestOverrideOperator();
    delete test;

    std::cout << "-------调用全局operator new和全局operator delete------" << std::endl;
    TestOverrideOperator *test1 = ::new TestOverrideOperator();
    ::delete test1;

    std::cout << "-------数组测试:调用重载operator new[]和重载operator delete[] ------" << std::endl;
    TestOverrideOperator* testArray = new TestOverrideOperator[3]();
    delete[] testArray;

    std::cout << "------- 数组测试:调用全局operator new[]和全局operator delete[] ------" << std::endl;
    TestOverrideOperator* testArray1 = ::new TestOverrideOperator[3]();
    ::delete[] testArray1;
}

         因为new和delete本身称为关键字或者操作符,所以类中的operator new和operator delete叫作重载operator new和operator delete操作符,但是这里将重载后的operator new和operator delete称为成员函数也没问题。

(1)为数组分配内存

        从上面的代码可以看出:operator new[]和operator delete[]只会被调用1次,但是类的构造函数和析构函数会被分别调用3次,这一点千万别搞错,不要误以为3个元素大小的数组new的时候就会分配3次内存,而delete也会执行3次。

        将断点设置在operator new[]函数体内,调试起来,观察形参size的值,发现是7。为什么会是7呢?因为这里创建的是3个对象的数组,每个对象占1字节,3个对象正好占用3字节(如下图,地址为0x016EEB44)。另外4字节是做什么用的呢?其实是记录数组大小的,数组大小为3,所以,这4字节(一个int或者unsigned int类型数据的大小)里面记录的内容就是3(如下图,地址为0x016EEB40),可以想象,释放数组内存的时候必然会用到这个数字(3),通过这个数字才知道new和delete时数组的大小是多少,从而知道调用多少次类的构造函数和析构函数。

2.重载全局operator new和operator delete操作符

        也可以重载全局的operator new、operator delete以及operator new[]、operator delete[],当然,在重载这些全局函数的时候,一定要放在全局空间里,不要放在自定义的命名空间里。

        虽然可以重载全局的operator new、operator delete、operator new[]、operator delete[],但很少有人这样做,因为这种重载影响面太广。读者知道有这样一回事就行了。一般都是重载某个类中的operator new、operator delete,这样影响面比较小(只限制在某个类内),也更实用。

        类中的重载会覆盖掉全局的重载。

四、内存池

        使用malloc这种分配方式来分配内存会产生比较大的内存浪费,尤其是频繁分配小块内存时,浪费更加明显。所以一个叫作“内存池”的词汇就应运而生。

1.内存池要解决的主要问题是什么?

  • 减少malloc调用次数,这意味着减少对内存的浪费。
  • 减少对malloc的调用次数后,能不能提高程序的一些运行效率或者说是运行速度呢?从某种程度上来说,能,但是效率提升并不太多,因为malloc的执行速度其实是极快的。

        减少内存浪费是根本,提高程序运行效率是顺带(不是最主要的)的。

2.内存池的实现原理是什么呢?

        就是用malloc申请一大块内存,分配内存的时候,就从这一大块内存中一点点分配给程序员,当一大块内存差不多用完的时候,再申请一大块内存,然后再一点一点地分配给程序员使用。

五、定位new

        除了传统new之外,还有一种new叫作“定位new”,翻译成英文就是placement new,因为它的用法比较独特,所以并没有对应的placement delete的说法。

        定位new的功能是:在已经分配的原始内存中初始化一个对象。

  • 已经分配,意味着定位new并不分配内存,也就是使用定位new之前内存必须先分配好。
  • 初始化一个对象,也就是初始化这个对象的内存,可以理解成其实就是调用对象的构造函数。

        总而言之,定位new就是能够在一个预先分配好的内存地址中构造一个对象。 定位new的格式如下:

new(分配好的内存首地址) 类类型(参数)
void  TestNew()
{
    student *s = new student();
    int *p= new(s) int(10);    //new(分配好的内存首地址) 类类型(参数)
    std::cout << *p << std::endl;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1102534.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通信系统中ZF,ML,MRC以及MMSE四种信号检测算法误码率matlab对比仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1、ZF&#xff08;零迫&#xff09;算法 4.2、ML&#xff08;最大似然&#xff09;算法 4.3、MRC&#xff08;最大比合并&#xff09;算法 4.4、MMSE&#xff08;最小均方误差&#xff…

2024杭州人工智能展会(世亚智博会)一场人工智能领域的视觉盛宴

2024年&#xff0c;一场规模空前的人工智能盛会将在杭州国际博览中心盛大开幕。这场名为“2024杭州国际人工智能展览会&#xff08;简称&#xff1a;世亚智博会&#xff09;”的展会活动&#xff0c;将于4月份在杭州国际博览中心隆重举行&#xff0c;届时将迎来一场人工智能领域…

网络安全必备常识:Web应用防火墙是什么?

如今&#xff0c;很多企业都将应用架设在Web平台上&#xff0c;为用户提供更为方便、快捷的服务支持&#xff0c;例如网上银行、网上购物等。与此同时&#xff0c;应用程序组合变得前所未有的复杂和多样化&#xff0c;这为攻击者发动攻击开辟了大量媒介&#xff0c;Web应用防火…

【C++ 学习 ㉘】- 详解 C++11 的列表初始化

目录 一、C11 简介 二、列表初始化 2.1 - 统一初始化 2.2 - 列表初始化的使用细节 2.2.1 - 聚合类型的定义 2.2.2 - 注意事项 2.3 - initializer_list 2.3.1 - 基本使用 2.3.2 - 源码剖析 一、C11 简介 1998 年&#xff0c;C 标准委员会发布了第一版 C 标准&#xff0…

使用new创建动态结构

在运行时创建数组优于在编译时创建数组&#xff0c;对于结构&#xff08;同一个结构可以存储多种类型的数据。&#xff09;也是如此。需要在程序运行时为结构分配所需的空间&#xff0c;这也可以使用new运算符来完成。通过使用new&#xff0c;可以创建动态结构。同样&#xff0…

【广州华锐互动】利用AR进行野外地质调查学习,培养学生实践能力

在科技发展的驱动下&#xff0c;AR&#xff08;增强现实&#xff09;技术已经在许多领域中找到了应用&#xff0c;包括医疗、教育、建筑和娱乐等。然而&#xff0c;有一个领域尚未充分利用AR技术的潜力&#xff0c;那就是野外地质调查。通过将AR技术引入到这个传统上需要大量人…

免费:实时 AI 编程助手 Amazon CodeWhisperer

点 &#xff0c;一起程序员弯道超车之路 现已正式推出实时 AI 编程助手 Amazon CodeWhisperer&#xff0c;包括 CodeWhisperer 个人套餐&#xff0c;所有开发人员均可免费使用。最初于去年推出的预览版 CodeWhisperer 让开发人员能够保持专注、高效&#xff0c;帮助他们快速、安…

rabbitMq (2)

RabbitMQ 消息应答与发布 文章目录 1. 消息应答1.2 自动应答1.2 手动应答1.3 代码案例 2. RabbitMQ 持久化2.1 队列持久化2.2 消息持久化 3. 不公平分发4. 预取值分发5. 发布确认5.1 发布确认逻辑5.2 开启发布确认的方法5.3 单个确认发布5.4 批量确认发布5.5 异步确认5.5.1 处理…

【milkv】更新rndis驱动

问题 由于windows升级到了11&#xff0c;导致rndis驱动无法识别到。 解决 打开设备管理器&#xff0c;查看网络适配器&#xff0c;没有更新会显示黄色的图标。 右击选择更新驱动

机器学习基础之《回归与聚类算法(3)—线性回归优化:岭回归》

一、什么是岭回归 其实岭回归就是带L2正则化的线性回归 岭回归&#xff0c;其实也是一种线性回归。只不过在算法建立回归方程时候&#xff0c;加上L2正则化的限制&#xff0c;从而达到解决过拟合的效果 二、API 1、sklearn.linear_model.Ridge(alpha1.0, fit_interceptTrue…

软件测试中bug修正后测试就结束了吗?

一般来说&#xff0c;当 Bug 跟踪系统上所有的 bug 都被关闭了以后&#xff0c;你会感到如释重负&#xff0c;终于可以松一口气了。 当项目成功交付后&#xff0c;你是否感到大脑进入了疲惫期&#xff0c;上网&#xff0c;聊天&#xff0c;写自己感兴趣的小程序&#xff0c;项…

SRS Config 一 基础配置

Config srs 流媒体服务配置官方文档已经很详细了&#xff0c;本文仅记录部分配置过程 srs.conf同级目录下 新建 self.conf 仿照srs.conf 添加基础配置 1 rtmp RTMP是直播的事实标准&#xff0c;这么多年以来一直是使用最广泛的直播协议。 然而Adobe公司没有一直更新RTMP协…

“1688按图搜索商品:拍立淘API,轻松实现高效购物!“

1688按图搜索商品&#xff08;拍立淘&#xff09;API的步骤大致如下&#xff1a; 需要先开放平台注册开发者账号。为每个淘宝应用注册一个应用程序键&#xff0c;登陆密钥。下载1688API的SDK并掌握基本的API基础知识和调用。利用SDK接口和对象&#xff0c;传入AppKey或者必要的…

VScode折叠代码

问题现状 代码看的我很烦&#xff0c; 有大段大段好像没有逻辑意义的部分&#xff0c;像大量的print语句&#xff0c; 想能不能折叠起来 在设置里面找 搜索Folding&#xff0c;找到Show Folding Controls&#xff0c; 换成always吧&#xff0c;一般默认是mouseover&#x…

21-数据结构-内部排序-交换排序

简介&#xff1a;主要根据两个数据进行比较从而交换彼此位置&#xff0c;以此类推&#xff0c;交换完全部。主要有冒泡和快速排序两种。 目录 一、冒泡排序 1.1简介&#xff1a; 1.2代码&#xff1a; 二、快速排序 1.1简介&#xff1a; 1.2代码&#xff1a; 一、冒泡排序…

10.17七段数码管单个多个(部分)

单个数码管的实现 第一种方式 一端并接称为位码&#xff1b;一端分别接收电平信号以控制灯的亮灭&#xff0c;称为段码 8421BCD码转七段数码管段码是将BCD码表示的十进制数转换成七段LED数码管的7个驱动段码&#xff0c; 段码就是LED灯的信号 a为1表示没用到a&#xff0c;a为…

文心一言 VS 讯飞星火 VS chatgpt (115)-- 算法导论10.2 8题

八、用go语言&#xff0c;说明如何在每个元素仅使用一个指针 x.np(而不是通常的两个指针 next和prev)的下实现双向链表。假设所有指针的值都可视为 k 位的整型数&#xff0c;且定义x.npx.next XOR x.prev&#xff0c;即x.nert和x.prev 的 k 位异或。(NIL 的值用0表示。)注意要说…

1数据结构的分类,算法效率的度量

一&#xff0c;数据结构的定义和分类 数据结构&#xff1a;数据之间的关系即数据的逻辑结构&#xff0c;因为要存储到计算机里&#xff0c;所以视为将这个数据的逻辑结构映射到存储器里。即数据因为自身的和其他的数据的关系而在计算机内存储的方式。我们就归类了一些类型。 二…

Idea怎么配置Maven才能优先从本地仓库获取依赖

网上的方法 : 在设置中搜索 Runner ,在VM Option中设置参数 -DarchetypeCataloginternal删除 解压后的依赖包中的 _remote.repositories m2e-lastUpdated.properties *.lastUpdated 文件。 上边都没有效果 最终的解决方法&#xff0c;修改maven配置文件settings.xml 主要两个…

封装一个Element-ui生成一个可行内编辑的表格(vue2项目)

这个封装的是一个供整个项目使用的表格,可多次复用.放在一个全局使用的公共组件文件下. 大致功能介绍,封装自定义指令,点击获得焦点,显示输入框,失去焦点显示文本内容,类型是字典决定类型,图片可以显示图片名还是上传图片 子组件 <script> export default {props: {//生…