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

news2024/11/15 20:08:15

在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++提供的面向对象特性和自动内存管理功能。

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

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

相关文章

2月线上速溶咖啡行业数据分析:“减肥咖啡”引领电商新潮流

随着生活节奏的加快&#xff0c;速溶咖啡因其便捷性受到广大消费者的青睐。不过&#xff0c;在如今世界咖啡市场激烈竞争的情况下&#xff0c;中国速溶咖啡市场也受到影响&#xff0c;增速有所放缓。 根据鲸参谋电商数据平台显示&#xff0c;2月线上综合电商&#xff08;京东天…

003_vector_conventions_in_MATLA中的向量约定

MATLAB中的向量约定 1. 前言 MATLAB是一种用于数值计算和数据可视化的高级编程语言。以前&#xff0c;都不好意思说它是编程语言&#xff0c;它实际上只是一个脚本工具&#xff0c;配套了一堆工具箱。比如Simulink&#xff0c;可以开展非常复杂的仿真&#xff0c;还能编译到实…

海外媒体发稿:出口贸易媒体发稿7个秘籍揭晓-华媒舍

出口贸易是许多国家经济增长的关键驱动力之一。不仅可以加快国家的发展步伐&#xff0c;还能为企业创造巨大的商机。如何能够在出口贸易中取得成功&#xff0c;如何能够引起媒体的关注&#xff0c;成为企业广告和宣传的焦点&#xff0c;是许多出口企业面临的挑战。本文将揭示出…

【LeetCode热题100】108. 将有序数组转换为二叉搜索树(二叉树)

一.题目要求 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 平衡二叉搜索树。 二.题目难度 简单 三.输入样例 示例 1&#xff1a; 输入&#xff1a;nums [-10,-3,0,5,9] 输出&#xff1a;[0,-3,9,-10,null,5] 解释&#x…

【Java程序设计】【C00367】基于(JavaWeb)Springboot的粮仓管理系统(有论文)

TOC 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;博客中有上百套程序可供参考&#xff0c;欢迎共同交流学习。 项目简介 项目获取 &#x1f345;文末点击卡片…

JVM——字符串常量池

在Java程序中String类的使用几乎无处不在&#xff0c;String类代表字符串&#xff0c;字符串对象可以说是Java程序中使用最多的对象了。首先&#xff0c;在Java中创建大量对象是非常耗费时间的。其次&#xff0c;在程序中又经常使用相同的字符串对象&#xff0c;如果每次都去重…

企业计算机服务器中了locked勒索病毒怎么处理?Locked勒索病毒解密流程

在网络技术不断发展应用过程中&#xff0c;越来越多的企业利用网络开展各项工作业务&#xff0c;网络为企业的生产运营提供了极大便利&#xff0c;但网络威胁手段也在不断增加&#xff0c;为企业的数据安全带来严重威胁。近日&#xff0c;新一波的网络勒索病毒比较猖獗&#xf…

云电脑火爆出圈,如何选择和使用?--腾讯云、ToDesk云电脑、青椒云使用评测和攻略

前言&#xff1a; Hello大家好&#xff0c;我是Dream。在当下&#xff0c;科技的飞速发展已经深入影响着我们的日常生活&#xff0c;特别是随着物联网的兴起和5G网络的普及&#xff0c;云计算作为一个重要的技术概念也逐渐走进了我们的视野。云计算早已不再是一个陌生的名词&am…

FANUC机器人零点标定的基本步骤(出厂数据)

FANUC机器人零点标定的基本步骤(出厂数据) FANUC 零点数据存在问题的机器人通常会出现以下几种报警: (1)SRVO-062报警 - 脉冲编码器数据丢失,机器人完全不能动,具体消除方法可参考以下链接中的内容: FANUC机器人SRVO-062报警原因分析及处理对策 (2)SRVO-075报警 -…

qrcode插件-生成二维码

安装 yarn add qrcodejs2 --save npm install qrcodejs2 --save 使用 <template><div><div id"qrcodeImg"></div><!-- 创建一个div&#xff0c;并设置id --></div> </template> <script> import QRCode from q…

9.串口通信

串口基本认识 串行接口简称串口&#xff0c;也称串行通信接口或串行通讯接口&#xff08;通常指COM接口&#xff09;&#xff0c;是采用串行通信方 式的扩展接口。串行接口&#xff08;Serial Interface&#xff09;是指数据一位一位地顺序传送。其特点是通信线路简 单&#x…

qt材料库---最新版(使用QsqlDatabase、QJsonObject)

将每种材料的参数设置保存为json,并将整个json语句放到数据库中,同时显示到列表中。 选中某行时,将参数反馈到控件上 源码 Material.h #pragma once #include "QtWindow.h" #include "ui_Widget.h" #include <QWidget> #include <QObject>…

Jetson AGX ORIN 配置 FGVC-PIM 神经网络(包含 arm64 下面 torch 和 torchvision 配置内容)

Jetson AGX ORIN 配置 FGVC-PIM 神经网络 文章目录 Jetson AGX ORIN 配置 FGVC-PIM 神经网络配置 ORIN 环境创建 FGVC-PIM 虚拟环境安装 PyTorch安装 torchvision安装其他依赖包 配置 ORIN 环境 首先先配置 ORIN 的环境&#xff0c;可以参考这个链接&#xff1a; Jetson AGX …

如何为企业策划一场XR虚拟直播?

活动年年办&#xff0c;都是老一套&#xff0c;想玩点新花样&#xff1f; 预算有限&#xff0c;但还是想把活动办的逼格高一点&#xff1f; 想通过活动&#xff0c;让更多的人知道自己企业的品牌&#xff1f; 随着AIGC技术的不断演变&#xff0c;企业活动的形式和内容也在不…

深入了解 Vue 3:性能与可用性的巨大提升

摘要&#xff1a;本文深入探讨了 Vue 3 相对于 Vue 2 在性能和可用性方面的重大改进&#xff0c;特别关注了虚拟 DOM 模块的重构&#xff08;静态提升&#xff09;、基于 Proxy 的响应式对象、事件缓存、更好的 Tree Shaking 支持、TypeScript 和 Monorepo 代码组织&#xff0c…

Java两地经纬度通过高德api获取两地距离(公里)

代码如下&#xff1a; String startLongitude entity.getLONGITUDE(); // 起点&#xff08;当前位置&#xff09;经度String startLatitude entity.getLATITUDE(); // 起点纬度String endLongitude entity.getLO(); // 终点经度String endLatitude entity.getLA(); …

vue-office/docx插件实现docx文件预览

1.下包 //预览docx文件 npm install vue-office/docx vue-demi//如果是vue2.6版本或以下还需要额外安装 vue/composition-api2.引入 <template><div>//在src填入文档地址<VueOfficeDocx srchttp://...../xx.docx style"width:80%" rendered"re…

Linux下的I/O模型

目录 一、什么是IO&#xff1f; 二、IO操作的两个阶段 三、五种I/O模型 1、阻塞I/O(blocking I/O) 2、非阻塞I/O(non-blocking I/O) 3、多路复用I/O(multiplexing I/O) 4、信号驱动I/O(signal-driven I/O) 5、异步I/O(asynchronous I/O) 四、五种I/O模型比较 一、什么…

【jvm】各个java版本默认的垃圾回收器

要看Java默认的垃圾回收器 可以使用以下命令 java -XX:PrintCommandLineFlags -version 各个java版本默认的垃圾回收器 从Java 1&#xff08;JDK 1.0&#xff09;开始到Java 21之间的各个Java版本默认的垃圾回收器经历了一系列的演变。以下是一些主要版本的Java默认垃圾回收…

Redis-指定配置启动

基础篇Redis 3.3.5.指定配置启动 如果要让Redis以后台方式启动&#xff0c;则必须修改Redis配置文件&#xff0c;就在我们之前解压的redis安装包下&#xff08;/usr/local/src/redis-6.2.6&#xff09;&#xff0c;名字叫redis.conf&#xff1a; 我们先将这个配置文件备份一份…