C++内存管理(2)new、delete详解

news2025/1/12 22:59:29

目录

new operator(new操作)

new类对象时加不加括号的差别

new工作任务

delete工作任务

new和delete 堆区空间操作(对比malloc和free)

new和delete操作基本类型的空间

new和delete操作基本类型的数组

new和delete操作类的空间

new和delete操作对象数组

new内存分配细节探秘

为什么要尽可能少的调用malloc?

new和delete的重载

为什么要重载 new

监测内存创建销毁,统计和监控泄漏

内存对齐的处理

特定应用:多进程内存共享

重载全局的 new 和 delete

在全局new和delete中添加定制行为

重载类的操作符 new 和 delete

类new和delete操作符重载基础

对齐的内存分配

共享内存的分配

定位new(placement new)

功能

使用placement new

placement new对象的销毁

placement new的应用

硬件编程

实现基础库

多种版本的operator new重载


new operator(new操作)

new类对象时加不加括号的差别

  • A *pa = new A;//有无构造函数初始化为垃圾值
  • A *pa2 = new A();//无构造函数初始化为0,有构造函数为垃圾值

在g++中默认初始化成员变量为0,而A *pa2 = new A(5)初始化成员变量为5

#include <iostream>
using namespace std;

class A
{
public:
    int m_num;

public:
    A(){};
    A(int num) : m_num(num)
    {
        cout << "construct" << endl;
    }
    ~A()
    {
        cout << "disconstruct" << endl;
    };
};

int main(int argc, char const *argv[])
{
    A *pa = new A;
    A *pa2 = new A();
    A *pa3 = new A(5);

    cout << "pa->m_num = " << pa->m_num << endl;
    cout << "pa2->m_num = " << pa2->m_num << endl;
    cout << "pa3->m_num = " << pa3->m_num << endl;

    delete pa;
    delete pa2;
    delete pa3;
    return 0;
}

运行结果:

new工作任务

调用operator new()--malloc

调用了分配对象的构造函数

delete工作任务

调用了分配对象的析构函数

调用operator delete()--free

new和delete 堆区空间操作(对比malloc和free)

new和delete操作基本类型的空间

new和malloc delete和free 没有区别

区别:

new 不用强制类型转换

new在申请空间的时候可以 初始化空间内容

new和delete操作基本类型的数组

new和delete操作类的空间

malloc不会调用构造函数 free不会调用析构函数

new 会调用构造函数 delete调用析构函数

new调用有参构造

new和delete操作对象数组

new内存分配细节探秘

  • new分配内存实际是调用malloc函数进行内存分配;
  • 思考:delete/free是如何知道要释放多大的内存?
    • 分配内存时,为了记录和管理分配出去的内存,额外多分配了不少内存,造成了浪费;尤其是你频繁的申请小块内存时,造成的浪费更明显,更严重
    • 实际分配情况

为什么要尽可能少的调用malloc?

  • 内存开销: 每次调用 malloc 都会引入额外的内存开销,包括内存分配表、堆管理等数据结构,这些开销可能会在大量小型分配时累积并消耗大量内存。
  • 内存泄漏风险: 使用 malloc 分配内存后,需要负责在不再使用内存时释放它。如果你频繁地调用 malloc,则需要管理和追踪许多不同的内存分配,容易出现内存泄漏问题,导致程序在运行时逐渐耗尽内存。
  • 性能开销: 内存分配和释放是相对较慢的操作,涉及到内部数据结构的维护、内存搜索等操作。频繁调用 malloc 可能会导致性能下降,特别是在大规模数据处理或高性能计算应用中。
  • 碎片化: 频繁分配和释放小块内存可能导致内存碎片化,即使系统总内存充足,也可能由于碎片化问题无法满足大块内存分配的需求。

为了减少 malloc 调用的次数,可以考虑以下方法:

  • 使用栈内存: 对于小型临时变量,可以使用栈内存而不是堆内存,因为栈内存的分配和释放非常快速。但要注意栈内存的生命周期通常较短。
  • 池化: 如果需要频繁创建和销毁对象,可以使用内存池技术,通过一次性分配一大块内存并自行管理对象的分配和释放。
  • 缓存: 对于某些可复用对象,可以使用缓存来避免频繁分配和释放内存。这在对象池等场景中很有用。
  • 避免不必要的动态分配: 如果可以在编译时确定数组或数据结构的大小,可以使用栈数组或静态分配来避免动态分配。

new和delete的重载

为什么要重载 new

监测内存创建销毁,统计和监控泄漏

在C++中,内存管理是开发者的一项重要责任,也是容易出错的地方。开发者可能会遗忘释放已分配的内存,导致内存泄漏。重载new和delete可以帮助开发者更好地追踪和管理内存分配。通过在重载的new和delete操作符中插入日志或者调试语句,开发者可以监测和记录所有内存分配和释放的情况,从而检测内存泄漏。

例如,以下的代码展示了如何重载new和delete操作符来监测和追踪内存分配:

void* operator new(size_t size) {
    void* p = malloc(size);
    std::cout << "Allocated " << size << " bytes at address " << p << std::endl;
    return p;
}

void operator delete(void* p) {
    std::cout << "Deallocated memory at address " << p << std::endl;
    free(p);
}

内存对齐的处理

在一些硬件平台和操作系统上,为了实现最优性能,数据需要按照某种特定的边界对齐。如果没有对齐,可能会导致性能下降,甚至运行错误。通过重载new和delete,我们可以为特定的类实现定制的内存对齐方式。

下面的代码演示了如何重载new和delete操作符来实现内存对齐:

class Aligned {
public:
    static void* operator new(std::size_t size) {
        void* p = std::aligned_alloc(alignof(Aligned), size);
        if (!p) {
            throw std::bad_alloc();
        }
        return p;
    }

    static void operator delete(void* p) {
        std::free(p);
    }
};

特定应用:多进程内存共享

在某些情况下,多个进程可能需要访问同一块内存区域。在这种情况下,可以通过重载new和delete操作符,实现在共享内存区域中分配和释放对象。

例如,以下的代码展示了如何通过重载new和delete来在共享内存中分配和释放对象:

// 假设SharedMemoryManager是一个用于管理共享内存的类
class SharedMemoryManager {
public:
    void* allocate(size_t size);
    void deallocate(void* p);
};

class SharedMemoryObject {
public:
    void* operator new(size_t size) {
        return SharedMemoryManager::allocate(size);
    }

    void operator delete(void* p) {
        SharedMemoryManager::deallocate(p);
    }
};

在以上的例子中,SharedMemoryObject类的对象将会被分配在共享内存中,从而可以被多个进程访问。

重载全局的 new 和 delete

全局的new和delete操作符可被重载以满足特定的需求,比如定制内存管理策略,或者为内存分配和释放添加自定义行为。要注意,这些全局重载将影响到整个程序的范围,包括标准库的容器等,所以在实践中应谨慎使用。

void* operator new(size_t size) {
    // ... 实现代码
}

void operator delete(void* p) {
    // ... 实现代码
}

operator new需要返回一个足够大,可以容纳请求内存大小的指针。如果内存分配失败,需要抛出std::bad_alloc异常。operator delete需要释放传入的指针指向的内存。

在全局new和delete中添加定制行为

下面的代码将在全局的new和delete操作符中添加一些定制的行为。在分配和释放内存时,我们会打印一些信息到控制台,以便于跟踪内存的使用情况:

#include <cstdlib>
#include <iostream>

void* operator new(size_t size) {
    void* p = std::malloc(size);
    if (!p) {
        throw std::bad_alloc();
    }

    std::cout << "Allocated " << size << " bytes at address " << p << std::endl;
    return p;
}

void operator delete(void* p) {
    std::cout << "Deallocated memory at address " << p << std::endl;
    std::free(p);
}

以上代码演示了如何在全局的new和delete操作符中添加自定义的行为。这种方式在实际开发中可以帮助我们更好地理解和跟踪内存的使用情况。不过请注意,添加过多的日志可能会对性能产生影响,所以在生产环境中通常不会添加过多的日志信息。

重载类的操作符 new 和 delete

对类的new和delete操作符进行重载允许我们为该类的对象提供定制的内存管理策略。这对于需要进行特殊内存管理的类来说特别有用,例如需要在共享内存中创建的对象,或者需要进行特殊对齐的对象。

类new和delete操作符重载基础

对类的new和delete操作符进行重载的基本形式如下:

class MyClass {
public:
    static void* operator new(std::size_t size);
    static void operator delete(void* p);
};

operator new需要返回一个足够大,可以容纳请求内存大小的指针。如果内存分配失败,需要抛出std::bad_alloc异常。operator delete需要释放传入的指针指向的内存。

对齐的内存分配

假设我们有一个需要8字节对齐的类,我们可以通过重载new和delete操作符来满足这个要求:

#include <cstdlib>
#include <new>

class Aligned {
public:
    static void* operator new(std::size_t size) {
        void* p = std::aligned_alloc(8, size);
        if (!p) {
            throw std::bad_alloc();
        }
        return p;
    }

    static void operator delete(void* p) {
        std::free(p);
    }
};

在这个例子中,我们使用了std::aligned_alloc函数来进行对齐的内存分配。如果分配失败,我们抛出std::bad_alloc异常。在operator delete中,我们简单地调用std::free来释放内存。

共享内存的分配

假设我们有一个需要在共享内存中创建的对象,我们可以通过重载new和delete操作符来实现:

// 假设SharedMemoryManager是一个用于管理共享内存的类
class SharedMemoryManager {
public:
    static void* allocate(std::size_t size);
    static void deallocate(void* p);
};

class SharedMemoryObject {
public:
    static void* operator new(std::size_t size) {
        return SharedMemoryManager::allocate(size);
    }

    static void operator delete(void* p) {
        SharedMemoryManager::deallocate(p);
    }
};

在这个例子中,SharedMemoryObject类的对象将会在共享内存中创建和销毁。这允许我们在多个进程间共享这些对象。

定位new(placement new)

放置new (placement new) 是一个特殊版本的new操作符,它允许程序员将对象创建在已经分配的内存上。换句话说,它允许我们"放置"一个新的对象在我们指定的、已经存在的内存位置上。

功能

在已经分配的原始内存中初始化一个对象;

  • 已经分配,定位new并不分配内存,你需要提前将这个定位new要使用的内存分配出来
  • 初始化一个对象(初始化一个对象的内存),调用这个对象的构造函数不再分配内存;

使用placement new

在普通的new操作中,首先会申请一块足够的内存,然后在这块内存上构造对象。但是在placement new中,内存必须已经存在,它只负责在指定的内存上构造对象。以下是一个使用placement new的例子:

#include <new> // 需要包含这个头文件来使用placement new

char buffer[1024]; // 预分配的内存

int* p = new (buffer) int(123); // 在buffer上放置一个int对象

对于类,placement new最好在我们需要使用的类中重载,否则在类外重载会影响到其它类型分配空间

#include <iostream>
using namespace std;

void *operator new(size_t size)
{
    void *p = malloc(size);
    std::cout << "Allocated " << size << " bytes at address " << p << std::endl;
    return p;
}

void operator delete(void *p)
{
    std::cout << "Deallocated memory at address " << p << std::endl;
    free(p);
}

class A
{
public:
    int m_num;

public:
    A(){cout<<"default construct"<<endl;};
    A(int num) : m_num(num)
    {
        cout << "construct" << endl;
    }
    ~A()
    {
        cout << "disconstruct" << endl;
    };
    void *operator new(size_t size, void *p)
    {
        cout << "placement new" << endl;
        return p;
    }
};

int main(int argc, char const *argv[])
{
    void *p = (void *)new char[sizeof(A)];
    A *pa = new (p) A();

    pa->m_num = 5;
    cout << *((int *)p) << endl;
    delete pa;
    return 0;
}

placement new对象的销毁

由于placement new仅仅在已经分配的内存上创建对象,而不会分配内存,所以当不再需要这个对象时,我们需要手动调用该对象的析构函数来销毁对象:

p->~int(); // 手动调用析构函数

需要注意的是,这里只销毁了对象,但并没有释放内存。内存的释放需要根据实际的情况来处理。例如,如果这块内存是在栈上分配的,那么当退出作用域时会自动释放;如果是在堆上分配的,那么可能需要手动释放。

placement new的应用

placement new的一个主要应用是当我们需要在特定的位置创建对象时,比如在已分配的堆内存上,或者在栈内存上,甚至在硬件指定的特定内存地址上。

此外,placement new也常用于实现自定义的内存池,内存池可以减少动态分配和释放内存带来的开销。我们可以预先分配一大块内存,然后通过placement new在这块内存上创建对象,从而提高内存使用的效率。

硬件编程

如果知道了硬件设备的地址,想要将那个硬件设备与一个C++类直接关联,那么定位new就非常有效了

通过将placement new可以将C++的类之间关联到硬件设备上,操作该对象就相当于操作硬件

如下面程序所示,假如操作STM32的GPIOB->GPIO_Pin1,假设GPIO_Pin1的存储器映射地址为0x00005600。由于类A的对象pa的地址就是对象pa内首个字段m_num的地址,因此操作m_num就相当于操作地址0x00005600。

#include <iostream>
using namespace std;

class A
{
public:
    int m_num;

public:
    A(){};
    A(int num) : m_num(num)
    {
        cout << "construct" << endl;
    }
    ~A()
    {
        cout << "disconstruct" << endl;
    };
};

int main(int argc, char const *argv[])
{
    //访问硬件:将C++的类之间关联到硬件设备上,操作该对象就相当于操作硬件
    //单片机/STM32/ARM9:操作硬件的物理地址就相等于操作该硬件
    //GPIOB->GPIO_Pin1
    void *p = (void*)0x00005600;
    A *pa = new(p) A();
    pa->m_num = 1;//拉高电平
    pa->m_num = 0;//拉低电平

    return 0;
}

实现基础库

基础库一般为了效率要先预分配内存,然后在预分配的内存上执行构造,几乎所有的C++容器都用到了定位new

多种版本的operator new重载

优先级:内部new、全局new

可以重载很多版本的operator new,只要每个版本参数不同就行,但是第一个参数是固定的,都是size_t,表示要new对象的sizeof值

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

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

相关文章

Layui快速入门之第二节布局容器(固定宽度与完整宽度)

目录 一&#xff1a;固定宽度 二&#xff1a; 完整宽度 一&#xff1a;固定宽度 将栅格放入一个带有 class"layui-container" 的特定容器中&#xff0c;以便在小屏幕以上的设备中固定宽度&#xff0c;让列可控(两侧有留白效果) <!--固定宽度(两侧有留白效果)--&…

Layui快速入门之第三节栅格布局

目录 一&#xff1a;栅格布局的基本概念 二&#xff1a;栅格布局规则 三&#xff1a;始终等比例水平排列案例 四&#xff1a;响应式规则 五&#xff1a;移动设备、桌面端的组合响应式展现案例 六&#xff1a;移动设备、平板、桌面端的复杂组合响应式展现案例 七&#xf…

Yalmip使用教程(6)-将约束条件写成矩阵形式

博客中所有内容均来源于自己学习过程中积累的经验以及对yalmip官方文档的翻译&#xff1a;https://yalmip.github.io/tutorials/ 这篇博客将详细介绍如何借助yalmip工具箱将约束条件写成矩阵形式。 1.相关函数介绍 1.1 depends和getvariables函数 depends和getvariables函数都…

C# Winform 简单排期实现(DevExpress TreeList)

排期的需求在很多任务安排的系统中都有相应的需求&#xff0c;原生的Winform控件并未提供相应的控件&#xff0c;一般都是利用DataGridViewTreeView组合完成相应的需求&#xff0c;实现起来比较麻烦。用过DevExpress控件集的开发者应该知道&#xff0c;DevExpress WinForm提供了…

数学建模--K-means聚类的Python实现

目录 1.算法流程简介 2.1.K-mean算法核心代码 2.2.K-mean算法效果展示 3.1.肘部法算法核心代码 3.2.肘部法算法效果展示 1.算法流程简介 #k-means聚类方法 """ k-means聚类算法流程: 1.K-mean均值聚类的方法就是先随机选择k个对象作为初始聚类中心. 2.这…

http实现文件分片下载

文章目录 检测是否支持HTTP Range 语法Range请求cURL示例单一范围多重范围条件式分片请求 Range分片请求的响应文件整体下载文件分片下载文本下载图片下载封装下载方法 HTTP分片异步下载是一种下载文件的技术&#xff0c;它允许将一个大文件分成多个小块&#xff08;分片&#…

一个新工具 nolyfill

名字的意思&#xff0c; 我自己的理解 no(po)lyfill 正如它的名字, 不要再用补丁了, 当然这里说的是过时的补丁。 polyfill 是补丁的意思 为什么要用这个插件 文档原文: 当您通过安装最新的 Node.js LTS 来接受最新的功能和安全修复时&#xff0c;像eslint-plugin-import、…

架构师如何做好需求分析

架构师如何做好需求分析 目录概述需求&#xff1a; 设计思路实现思路分析1.主要步骤 2.主要步骤2操作步骤 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,…

Android笔记(二十八):在雷电模拟器安卓7.0+上使用Charles抓包详细教程

背景 由于手头没有合适的真机,所有经常使用雷神模拟器来跑项目,模拟器也需要能够抓包看看接口返回的数据,以便自测调试。本文记录了如何在雷电模拟器安卓7.0+上使用Charles抓包,其他模拟器没试过。 最终效果 浏览器打开百度网页,能抓到百度页面数据 具体步骤 模拟器…

xinput1_3.dll丢失的解决方法,快速修复xinput1_3.dll文件

在使用电脑时&#xff0c;我们可能会遇到各种各样的问题&#xff0c;其中之一就是xinput1_3.dll文件丢失的情况。这个文件是DirectX的一部分&#xff0c;它对于许多游戏和其他应用程序的正常运行至关重要。当xinput1_3.dll文件丢失时&#xff0c;我们无法启动或运行依赖该文件的…

LeetCode刷题笔记【28】:贪心算法专题-6(单调递增的数字、监控二叉树)

文章目录 前置知识738.单调递增的数字题目描述解题思路代码 968.监控二叉树题目描述解题思路代码 总结 前置知识 参考前文 参考文章&#xff1a; LeetCode刷题笔记【23】&#xff1a;贪心算法专题-1&#xff08;分发饼干、摆动序列、最大子序和&#xff09; LeetCode刷题笔记【…

VMware虚拟机+Centos7 配置静态,动态IP

本章目录 一、查看网关&#xff1a; 编辑–>虚拟网络编辑器二、点击NAT设置三、记住网关IP待会要用四、配置静态ip地址1、进入存放修改IP地址的目录2、修改ip地址的文件3、编辑文件4、文件&#xff08;编辑好后退出&#xff09; 五、重启网络六、测试1、linux上查看IP地址的…

使用pyenv安装python缓慢或无法安装

使用pyenv安装python缓慢或无法安装 这一定程度上和网络情况有关&#xff0c;下面提供几个常见方法&#xff1a; 关闭 VPN 后重新安装使用管理员权限打开命令窗口后安装如下 手动安装 pyenv 在执行 pyenv install --- 命令的时候&#xff0c;会连接远程库&#xff0c;将要安…

格式工厂多个图片合并成一个PDF的报错

使用图片合并PDF功能时 当图片数量超过50会报错 找到imgconv.py文件&#xff0c;将50改为500&#xff0c;保存 现在可以支持100张图合并成一个PDF文件了&#xff01; 但是超过150张程序会直接闪退&#xff0c;正在解决中。。

基于任务队列的机器学习服务实现

将机器模型部署到生产环境的方法有很多。 常见的方法之一是将其实现为 Web 服务。 最流行的类型是 REST API。 它的作用是全天候&#xff08;24/7&#xff09;部署和运行&#xff0c;等待接收来自客户端的 JSON 请求&#xff0c;提取输入&#xff0c;并将其发送到 ML 模型以预测…

3D异常检测论文笔记 | Shape-Guided Dual-Memory Learning for 3D Anomaly Detection

文章目录 摘要一、介绍三、方法3.1. 形状引导专家学习3.2. Shape-Guided推理 摘要 我们提出了一个形状引导的专家学习框架来解决无监督的三维异常检测问题。我们的方法是建立在两个专门的专家模型的有效性和他们的协同从颜色和形状模态定位异常区域。第一个专家利用几何信息通…

涛然自得周刊(第 5 期):蝲蛄吟唱的地方

作者&#xff1a;何一涛 日期&#xff1a;2023 年 8 月 20 日 涛然自得周刊主要精选作者阅读过的书影音内容&#xff0c;不定期发。历史周刊内容可以看这里。 电影 《沼泽深处的女孩》 改编自小说《蝲蛄吟唱的地方》&#xff0c;主角是一位在沼泽地独自生活并长大的女孩&…

[VSCode] 替换掉/去掉空行

VSCode中使用快捷键CtrlH&#xff0c;出现替换功能&#xff0c;在上面的“查找”框中输入正则表达式&#xff1a; ^\s*(?\r?$)\n然后选择右侧的“使用正则表达式”&#xff1b;“替换”框内为空&#xff0c;点击右侧的“全部替换”&#xff0c;即可去除所有空行。 参考 [VS…

Apipost forEach控制器怎么用

最近&#xff0c;Apipost对自动化测试进行了优化&#xff0c;新增foreach控制器。这个新功能的引入为自动化测试带来了更高的效率和灵活性。本文将介绍Apipost的foreach控制器&#xff0c;解释其用途和优势&#xff0c;帮助您更好地利用这一功能提升自己的测试工作。 什么是fo…

Andorid项目源码(167套)

一、项目介绍 (精华)新浪微博图片缓冲技术_hyg.rar ActivityGroup GridView ViewFlipper 实现选项卡.zip Adroid UI 界面绘制原理分析.rar AnderWeb-android_packages_apps_Launcher-4458ee4.zip andorid 源码北京公交线路查询&#xff08;离线&#xff09;.zip android Gal…