C++动态内存管理:new/delete与malloc/free的对比

在C++中,动态内存管理是一个至关重要的概念。它允许我们在程序运行时根据需要动态地分配和释放内存,为对象创建和销毁提供了灵活性。在C++中,我们通常会用到两对工具:new/delete 和 malloc/free。虽然它们都能够完成类似的任务,但在使用、安全性和灵活性方面存在显著差异。

new/delete

  • 类型安全性: newdelete提供了更好的类型安全性。在使用new创建对象时,会自动调用构造函数进行初始化,而在释放内存时,会自动调用析构函数,确保资源正确释放,避免内存泄漏和资源泄漏的风险。

  • 异常处理: new在分配内存失败时会抛出std::bad_alloc异常,这使得在内存分配失败时更容易处理错误情况,从而增强了程序的健壮性。

  • 自动资源管理: newdelete提供了自动资源管理的功能,使得在对象的生命周期结束时能够正确地释放内存,而不需要手动管理。

使用new和delete

#include <iostream>  
  
class MyClass {  
public:  
    MyClass() {  
        std::cout << "  MyClass()" << std::endl;  
    }  
      
    ~MyClass() {  
        std::cout << "~MyClass()" << std::endl;  
    }  
      
    void show() {  
        std::cout << "Hello!" << std::endl;  
    }  
};  
  
int main() {  
    // 使用new分配内存并创建对象  
    MyClass* obj = new MyClass();  
    obj->show(); // 输出: Hello!  
      
    // 使用delete释放内存并调用析构函数  
    delete obj; // 输出: ~MyClass() 
    
    //数组的用法
    int* pa=new int[6];
    delete[] pa;  
    
    return 0;  
}

malloc/free

  • 内存分配malloc函数根据请求的大小分配一块连续的内存区域,并返回指向该内存的指针。它不会自动初始化内存区域。
  • 内存释放free函数用于释放之前通过malloc分配的内存。它不会调用析构函数或执行任何清理操作。
  • 错误处理:如果malloc无法分配所需的内存,它会返回NULL

使用malloc和free

#include <iostream>  
#include <stdlib.h> // 为了使用malloc和free  
  

int main() {  
    // 使用malloc分配内存(但不调用构造函数)  
    void* memory = malloc(sizeof(MyClass));  
    if (memory == nullptr) {  
        std::cerr << "Memory allocation failed." << std::endl;  
        return 1;  
    }  
      
    // 使用"placement new"在已分配的内存上构造对象  
    MyClass* obj = new(memory) MyClass();  
    obj->show(); // 输出:Hello! 
      
    // 手动调用析构函数来销毁对象(但不释放内存)  
    obj->~MyClass(); // 输出: ~MyClass() 
      
    // 使用free释放之前通过malloc分配的内存  
    free(memory);  
      
    return 0;  
}

mallocfree是C语言中的函数,虽然它们可以在C++中使用,但通常不推荐,主要原因包括:

  • 不安全的类型转换: malloc返回void*指针,需要手动进行类型转换,容易引起错误。

  • 无构造和析构函数调用: mallocfree只是简单地分配和释放内存,并不调用对象的构造和析构函数。这可能导致对象状态未初始化或未正确清理,容易引发潜在的bug。

  • 不便的异常处理: malloc在分配内存失败时返回NULL,需要手动检查返回值,这在处理错误时不如C++的异常机制方便。

operator new 和 operator delete 函数

在 C++ 中,动态内存管理是至关重要的,而 operator newoperator delete 这两个全局函数则承担了其中的重要角色。它们与 newdelete 操作符紧密相关,负责在底层进行动态内存的分配和释放。让我们深入了解这两个函数的功能和工作原理。

operator new

operator new 的主要任务是分配指定大小的内存块。当使用 new 操作符为一个对象动态分配内存时,实际上会调用 operator new 函数来执行内存分配。这个函数会返回一个指向新分配内存的指针。如果内存分配失败,它通常会抛出一个 std::bad_alloc 异常,除非特别指定了不同的行为。

在标准的实现中,operator new 通常会调用底层的 malloc 函数来实际分配内存,但开发者也可以重载 operator new 以提供自定义的内存分配行为。

operator delete

operator new 相对应,operator delete 的任务是释放之前通过 operator new 分配的内存。当使用 delete 操作符释放一个对象时,实际上会调用 operator delete 函数来执行内存释放。这个函数通常会调用底层的 free 函数来回收内存。

operator new 类似,开发者也可以重载 operator delete 以提供自定义的内存释放行为。

//调用构造和析构
	A* p = (A*)::operator new(sizeof(A)); //分配
    new(p) A(); //构造
    p->~A();   //析构
    ::operator delete(p); //释放

    //调用构造和析构
	A* p1 = new A;
	delete p1;

	//不会调用构造和析构
	A*p2 = (A*)operator new(sizeof(A));
	operator delete(p2);

operator new 和 operator delete 源码:
在这里插入图片描述
详细代码,以及示例可以到[cplusplus]查看(https://legacy.cplusplus.com/reference/new/operator%20new/?kw=operator%20new)

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,
尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	//try to allocate size bytes
	void *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 delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData)
{
	_CrtMemBlockHeader * pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK); /* block other threads */
	__TRY
	/* get a pointer to memory block header */
	pHead = pHdr(pUserData);
	/* verify block type */
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
	_free_dbg( pUserData, pHead->nBlockUse );
	__FINALLY
	_munlock(_HEAP_LOCK); /* release other threads */
	__END_TRY_FINALLY
	return;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

operator new 的三种形式

  1. throwing

    void* operator new(std::size_t size) throw (std::bad_alloc);
    
    • 当分配失败时会抛出 std::bad_alloc 异常。
  2. nothrow

    void* operator new(std::size_t size, const std::nothrow_t& nothrow_value) throw();
    
    • 当分配失败时返回 nullptr,不会抛出异常。
  3. placement

    void* operator new(std::size_t size, void* ptr) throw();
    
    • 在指定的 ptr 地址上分配内存,不会抛出异常。
    • 这种形式通常用于在已分配的内存上构造对象,例如在内存池中使用。

placement new 的应用

  • 通过在已分配的内存上构造对象,实现在内存池中的对象管理。
  • 调用形式为 new(p) A();,其中 p 可以是动态分配的内存也可以是栈中的缓冲区。
#include <new>

// 示例:在已分配的内存上构造对象
void* mem = operator new(sizeof(A)); // 从内存池中获取内存
A* obj = new(mem) A(); // 在 mem 地址上构造 A 对象

placement new 只是返回传入的指针 ptr,不会进行内存分配,而是通过调用对象的构造函数在指定的地址上创建对象。

重载 operator newoperator delete

在某些应用场景中,开发者可能需要实现自定义的内存分配和释放策略,比如使用内存池、堆栈分配器或共享内存等。这时,可以通过重载 operator newoperator delete 来实现。

重载 operator new

在 C++ 中,operator new 可以被重载,允许开发者自定义内存分配方式。在某些应用场景中,开发者可能需要实现自定义的内存分配和释放策略,比如使用内存池、堆栈分配器或共享内存等。这时,可以通过重载 operator new 和 operator delete 来实现。

class A {
public:
    A() {
        std::cout << "A()" << std::endl;
    }

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

    // 重载 operator new
    void* operator new(size_t size) {
        std::cout << " A() operator new" << std::endl;
        return malloc(size);
    }

    // 重载 operator new 以支持 nothrow
    void* operator new(size_t size, const std::nothrow_t& nothrow_value) {
        std::cout << "A() operator new nothrow" << std::endl;
        return malloc(size);
    }
};

int main() {
    A* p1 = new A;  // 调用 A::operator new
    delete p1;

    A* p2 = new(std::nothrow) A;  // 调用 A::operator new nothrow
    delete p2;

    return 0;
}

运行结果:

A() operator new
A() 
~A() 
A() operator new nothrow
A() 
~A() 

如果类 A 中没有定义对 operator new 的重载,那么 new Anew(std::nothrow) A 都将会使用全局的 operator new(size_t size)

自定义参数的 operator new 重载

operator new 重载可以添加自定义参数,这些参数可以在分配内存时传递。虽然这些参数在标准的 operator new 中没有实际用途,但可以用于调试和检测。

void* operator new(size_t size, int x, int y, int z) {
    std::cout << "X=" << x << "  Y=" << y << " Z=" << z << std::endl;
    return malloc(size);
}

这种重载看起来可能没有太大的实际作用,因为标准的 operator new 只需完成内存分配任务。然而,通过对这类重载的巧妙应用,可以在动态内存分配的调试和检测中发挥作用。

placement new

placement new 本身是 operator new 的一个重载,它允许在已分配的内存上构造对象,常用于内存池等场景。其调用形式为 new(ptr) A();,其中 ptr 是已分配内存的指针。

void* operator new(size_t size, void* ptr) {
    return ptr; // 只是简单返回指针,不进行实际内存分配
}

一般情况下,不建议修改 placement new 的实现,因为它通常与 new(ptr) A(); 配合使用,其职责只需简单返回指针。

malloc/free和new/delete的区别

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

在C++中,一般推荐使用newdelete进行动态内存管理,因为它们提供了更好的类型安全性,并能自动调用构造函数和析构函数,从而减少了内存泄漏和资源泄漏的风险。

什么是内存泄漏,内存泄漏的危害

  • 内存泄漏是指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

  • 内存泄漏的危害在于长期运行的程序出现内存泄漏会影响很大,例如操作系统、后台服务等。出现内存泄漏会导致系统资源越来越稀缺,进而影响系统的响应速度,最终可能导致系统性能下降甚至卡死的情况发生。内存泄漏问题是软件开发中常见但危害严重的问题之一,因此在开发过程中应该严格注意内存管理,避免内存泄漏的发生。

然而,在需要与C代码交互或需要更底层的内存管理控制时,mallocfree可能会被用到。但在这种情况下,需要格外小心以确保正确地管理内存和对象的生命周期。

需要注意的是,尽管mallocfree可以在C++中使用,但并不推荐在C++中经常使用它们进行动态内存管理。因为这可能会导致一些问题,比如忘记初始化或清理对象的资源,从而导致内存泄漏或其他问题。相反,应该优先考虑使用newdelete来利用C++提供的面向对象特性和自动内存管理功能。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

「10」文本(GDI+):添加文字,可设置背景添加移动效果

「10」文本&#xff08;GDI&#xff09;添加文字&#xff0c;可设置背景添加移动效果 在OBS软件里&#xff0c;通过来源组件「文本&#xff08;GDI&#xff09;」&#xff0c;您可以添加任意您想要呈现的文字&#xff0c;在直播窗口中显示&#xff0c;它可以是提示语、广告词、…

【双指针】Leetcode 盛最多水的容器

题目解析 11. 盛水最多的容器 木桶效应&#xff0c;寻找一个区间使得这个区间的体积最大 算法讲解 1. 暴力枚举 遍历这个容器&#xff0c;将每一个区间的体积求出来&#xff0c;然后找出最大的 class Solution { public:int maxArea(vector<int>& height){int n…

【技术总结】常用指标mAP, mIoU, mDice, mFscore, aAcc 实现

mAP mAP 全称是 mean Average Precision. 其中 mean 这个操作是在 class 级别上, 因此只需要将所有类别平均即可. 所有需要关注的就是 AP. AP 是 Precision-Recall 曲线和坐标轴围成的面积. 提到曲线可能会感觉比较懵 – 模型的预测对或者不对都是确定的, 哪里来的曲线呢? 想…

字节算法岗二面,凉凉。。。

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 汇总…

php反序列化刷题1

[SWPUCTF 2021 新生赛]ez_unserialize 查看源代码想到robots协议 看这个代码比较简单 直接让adminadmin passwdctf就行了 poc <?php class wllm {public $admin;public $passwd; }$p new wllm(); $p->admin "admin"; $p->passwd "ctf"; ec…

【第三方登录】Google邮箱

登录谷歌邮箱开发者 https://console.developers.google.com/ 先创建项目 我们用的web应用 设置回调 核心主要&#xff1a; 1.创建应用 2.创建客户端ID 3.设置域名和重定向URL 4.对外公开&#xff0c;这样所有的gmail邮箱 都能参与测试PHP代码实现 引入第三方包 h…

【云能耗管理系统在某大型商场的应用】安科瑞Acrel-EIOT能源物联网平台方案

摘要&#xff1a;依据对上海市某大型商场现场考察的结果&#xff0c;提出通过建设云能耗管理系统的方案来改善商场能耗的管理现状。首先充分搜集建筑信息和设备运行工况&#xff0c;合理设计系统实施方案&#xff0c;解决现场数据采集和传输障碍&#xff0c;完成云能耗管理系统…

常用设计模式介绍

前言 简说设计模式。 文章目录 前言一、设计模式的要素1、设计模式解决的问题2、设计模式分类1&#xff09;创建型设计模式2&#xff09;结构型设计模式3&#xff09;行为型设计模式 二、详细介绍1、创建型设计模式1&#xff09;工厂方法模式2&#xff09;抽象工厂模式3&#x…

【JavaEE】进程是什么?

文章目录 ✍进程的概念✍进程存在的意义✍进程在计算机中的存在形式✍进程调度 ✍进程的概念 每个应⽤程序运⾏于现代操作系统之上时&#xff0c;操作系统会提供⼀种抽象&#xff0c;好像系统上只有这个程序在运⾏&#xff0c;所有的硬件资源都被这个程序在使⽤。这种假象是通…

上位机图像处理和嵌入式模块部署(qmacvisual拟合圆和拟合椭圆)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们学习了拟合直线&#xff0c;今天继续学习下拟合圆和拟合椭圆。其实除了最后一步不同&#xff0c;两者的逻辑是差不多的。一般都是&#xf…

C语言例4-6:格式字符d的使用例子

代码如下&#xff1a; //格式字符d的使用例子 #include<stdio.h> int main(void) {int num1123;long num2123456;printf("num1%d,num1%5d,num1%-5d,num1%2d\n",num1,num1,num1,num1);//以四种不同格式&#xff0c;输出int型数据num1的值printf("num2%ld,…

Elasticsearch 索引模板、生命周期策略、节点角色

简介 索引模板可以帮助简化创建和二次配置索引的过程&#xff0c;让我们更高效地管理索引的配置和映射。 索引生命周期策略是一项有意义的功能。它通常用于管理索引和分片的热&#xff08;hot&#xff09;、温&#xff08;warm&#xff09;和冷&#xff08;cold&#xff09;数…

【研发管理】产品经理知识体系-战略

导读&#xff1a;了解和掌握产品经理知识体系-战略是产品经理必修课。战略在产品创新管理框架中核心位置。本文概要梳理战略相关知识内容&#xff0c;仅供大家参考。 目录 1、战略定义 1.1 战略金字塔 1.2 战略的层级总表 1.3 战略跟战术的关系 1.4 愿景、使命和价值观​编…

ExoPlayer架构详解与源码分析(12)——Cache

系列文章目录 ExoPlayer架构详解与源码分析&#xff08;1&#xff09;——前言 ExoPlayer架构详解与源码分析&#xff08;2&#xff09;——Player ExoPlayer架构详解与源码分析&#xff08;3&#xff09;——Timeline ExoPlayer架构详解与源码分析&#xff08;4&#xff09;—…

yolov8直接调用zed相机实现三维测距(python)

yolov8直接调用zed相机实现三维测距&#xff08;python&#xff09; 1. 相关配置2. 相关代码3. 实验结果 相关链接 此项目直接调用zed相机实现三维测距&#xff0c;无需标定&#xff0c;相关内容如下&#xff1a; 1.yolov5直接调用zed相机实现三维测距&#xff08;python&#…

2024年哈尔滨工业大学材料科学与工程学院硕士研究生招生复试名单

2024年哈尔滨工业大学材料科学与工程学院硕士研究生招生复试名单 材料科学与工程学院2024年硕士研究生招生考试复试及录取工作方案 &#xff08;含深圳、威海校区&#xff0c;不含航天学院复合材料方向&#xff09; 复试录取名单数据分析: {51412, 50222, 50242, 61121, 50251…

蓝桥杯单片机快速开发笔记——利用定时器计数器设置定时器

一、基本原理 参考本栏http://t.csdnimg.cn/iPHN0 二、具体步骤 三、主要事项 如果使用中断功能记得打开总中断EA 四、示例代码 void Timer0_Isr(void) interrupt 1 { }void Timer0_Init(void) //10毫秒12.000MHz {AUXR & 0x7F; //定时器时钟12T模式TMOD & 0xF0;…

创建linux虚拟机系统:(安装Ubuntu镜像文件,包含语言设置、中文输入法、时间设置)

我下载的是清华大写开源软件镜像站中的ubuntu-20.04.6-desktop-amd64.iso这个镜像文件&#xff0c; 这个文件我下载完成之后没有解压&#xff0c;直接在创建虚拟机的时候选择的压缩包。 地址为&#xff1a;Index of /ubuntu-releases/20.04/ | 清华大学开源软件镜像站 | Tsin…

FastAPI+React全栈开发05 React前端框架概述

Chapter01 Web Development and the FARM Stack 05 The frontend React FastAPIReact全栈开发05 React前端框架概述 Let’s start with a bit of context here. Perhaps the changes in the world of the web are most visible when we talk about the frontend, the part o…

政安晨:【Keras机器学习实践要点】(三)—— 编写组件与训练数据

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras实战演绎机器学习 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 介绍 通过 Keras&#xff0c;您可以编写自定…