【C++精华铺】7.C++内存管理

这篇具有很好参考价值的文章主要介绍了【C++精华铺】7.C++内存管理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

1. C语言动态内存管理

2. C++内存管理方式

2.1 new/delete和new T[]/delete[]

 2.1.1 操作内置类型

2.1.2 操作自定义类型

2.2 new/delete和new T[]/delete[]的原理

2.2.1 原理

2.2.2 operator new和operator delete        

2.2.3 new T[]的特殊处理(可以不看)

2.3  定位new表达式(了解)

 3. C语言 malloc/free和C++ new/delete的区别(面试常考)

1. C语言动态内存管理

        C++的内存管理方式和C语言的内存管理方式存在千丝万缕的关系,如果对C语言内存管理不熟悉的可以先去看看这篇文章:

动态内存分配—malloc calloc realloc free 和常见动态内存错误_子亦半截诗的博客-CSDN博客https://zi-yi.blog.csdn.net/article/details/127347451?spm=1001.2014.3001.5502

2. C++内存管理方式

        C++继承了C语言的内存管理方式,但是C语言的内存管理方式在C++中显得心有余而力不足,在涉及内存管理的自定义类型的内存释放上就显得无能为力。C++引入了new/delete和new T[]/delete[]的内存管理方式。在正式学习C++的内存管理方式之前先重温一下C/C++的内存分布:

【C++精华铺】7.C++内存管理,C++,c++,开发语言

2.1 new/delete和new T[]/delete[]

 2.1.1 操作内置类型

        new/delete和new T[]/delete[]在操作内置类型的时候和malloc申请空间的方式基本相同,但是new/delete和new T[]/delete[]在开辟空间的同时可以进行初始化。C语言中开辟空间用malloc等,释放空间用free,C++中开辟空间用new/new T[],释放空间用delete/delete[]。new/delete用来开辟和释放单个对象的空间,new T[]/delete[]用来开辟和释放多个连续对象的空间。(new和delete,new T[]和delete[]必须配套使用,不能交叉使用)

int main()
{
	//C++98
	//动态申请一个int类型的空间;
	int* p1 = new int;
	delete p1;
	//动态申请一个int类型的空间并且初始化成1;
	int* p2 = new int(1);
	delete p2;
	//动态开辟十个int类型的空间;
	int* p3 = new int[10];
	delete[] p3;
	//动态开辟十个int类型的空间并全部初始化成0(不支持逐个初始化);
	int* p4 = new int[10]();
	delete[] p4;
	
	//C++11 支持了列表初始化
	//动态申请一个int类型的空间并且初始化成0;
	int* p5 = new int{};
	delete p5;
	//动态申请一个int类型的空间并且初始化成1;
	int* p6 = new int{1};
	delete p6;
	//动态开辟十个int类型的空间并全部初始化成0;
	int* p7 = new int[10] {};
	delete[] p7;
	//动态开辟十个int类型的空间,前面三个空间分别初始化成1,2,3,后面7个元素初始化成0;
	int* p8 = new int[10] {1, 2, 3};
	delete[] p8;
}

2.1.2 操作自定义类型

        new/delete和new T[]/delete[]在操作内置类型的时候和malloc几乎没有区别,但是new/delete和new T[]/delete[]在操作自定义类型的时候除了开空间还会调用构造函数和析构函数。如下:

class Date
{
public:
	Date(int year = 2023, int month = 8, int day = 14)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year = 2023, int month = 8, int day = 14)" << endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date *d1 = (Date*)malloc(sizeof(Date)); //开空间
	free(d1);  //释放空间

	cout << "----------------------------------" << endl;

	Date* d2 = new Date(); //开空间后调用构造
	delete d2;   //调用析构后释放空间


	cout << "----------------------------------" << endl;

	Date* d3 = new Date[10];  //开十个Date大小的空间,调用十次构造函数。
	delete[] d3;   //调用十次析构函数,然后释放空间。
}

输出:

【C++精华铺】7.C++内存管理,C++,c++,开发语言

         根据结果就可知new和delete不仅仅开空间和释放空间,并且调用了构造和析构。在new的时候new会先开空间然后调用构造函数,而delete会先调用析构函数然后再释放。

2.1.3 

2.2 new/delete和new T[]/delete[]的原理

2.2.1 原理

        在我们讲解new/delete之前我们要引入另外俩个函数,也就是operator new和operator delete,因为new的底层就会调用这个函数,不说废话直接上结论:

  1. new的原理:调用operator new函数申请空间;然后再申请的空间调用构造函数初始化对象。
  2. delete的原理:在空间上执行析构函数;调用operator delete释放空间。
  3. new T[N]的原理:调用operator new[](实际调用operator new函数完成N个对象空间的申请);然后在申请的空间上执行N次构造函数。
  4. delete[]的原理:在申请的空间上执行N次析构函数;调用operator delete[](实际调用operator delete函数完成N个对象空间的析构)完成析构。

2.2.2 operator new和operator delete        

        我们发现无论是new/new T[]还是delete/delete[]最后都调用了这operator new和operator delete,那operator new和operator delete究竟是何方神圣?其实operator new和operator delete是对malloc和free的封装,这俩个函数分别都有三种不同的重载形式。

  • throwing(1) void* operator new (std::size_t size) throw (std::bad_alloc);
  • nothrow(2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
  • placement(3) void* operator new (std::size_t size, void* ptr) throw();
  • ordinary(1) void operator delete (void* ptr) throw();
  • nothrow(2) void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) throw();
  • placement(3) void operator delete (void* ptr, void* voidptr2) throw();

        对于这几个函数我们没必要做过多的深究(容易走火入魔),性价比不高。所以我们只会带大家了解一下俩个函数的第一种重载形式,其他的我只会一句带过。

        1. throwing(1) void* operator new (std::size_t size) throw (std::bad_alloc);这个函数的底层实际是通过malloc来申请空间,当malloc申请空间成功就会直接返回空间的地址,如果是失败,会尝试执行空间不足的应对措施,如果用户设置了应对措施就会继续申请,否则就会抛bad_alloc异常。(对于异常如果不了解的同学只需记住就可以,以后会学习)

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);
}

         throwing(1)nothrow(2)的区别就是nothrow(2)申请空间失败不会抛异常,而是返回空指针。 

        2. ordinary(1) void operator delete (void* ptr) throw();这个函数底层调用的是free来释放空间,使指针位置无效,解除内存分配。

/*
#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)
*/
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);   //调用free
	__FINALLY
		_munlock(_HEAP_LOCK);  /* release other threads 释放其他线程*/
	__END_TRY_FINALLY
		return;
}

        nothrow(2)和ordinary(1)功能完全相同,placement(3)也叫做放置删除,不会做任何处理。

         3. operator new/operator delete的使用:

        operator new/operator delete的使用方式和malloc/free完全相同,不同在于operator new/operator delete申请失败抛异常,malloc/free申请失败返回NULL。

int main()
{
	int* p1 = (int*)malloc(sizeof(int));
	free(p1);

	int* p2 = (int*)operator new(sizeof(int));
	operator delete(p2);
}

2.2.3 new T[]的特殊处理(可以不看)

        (这里大家可以跳过,因为不同编译器处理方式不同,不是特别需要去了解)在我们使用malloc/free、new/delete、new T[]/delete[] 的时候我们发现有的时候在一些情况下是可以交叉使用的,如果不涉及到调用构造和调用析构不会造成内存泄漏的情况下new和free、malloc和delete是可以交叉使用的(强烈不建议交叉使用,直接从根本上杜绝内存泄漏)像下面这种可以使用而且也不会造成内存泄漏,编译器是支持这样写的,但还是不建议。

int main()
{
	int* p = new int();
	free(p);

	int* p1 = (int*)malloc(sizeof(int));
	delete p1;
}

        对于自定义类型malloc/free、new/delete交叉使用由于free不会调用析构函数会造成内存泄漏,但是编译器也不会报错,也就是说少调用了析构函数造成内存泄漏编译器是不会报错的,无非就是浪费资源了呗。如下:

class Date
{
public:
	Date(int year = 2023, int month = 8, int day = 14)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year = 2023, int month = 8, int day = 14)" << endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};


int main()
{
	Date* d1 = new Date();
	free(d1);
}

输出:

【C++精华铺】7.C++内存管理,C++,c++,开发语言

         但是当我们将(malloc/free、new/delete)和 new T[]/delete[]交叉使用时,编译器会直接报错,这里难道不只是少调用了析构函数的原因吗?根据上面的案例我们就知道少调用析构造成内存泄漏是不会报错的。那这里报错的原因是什么呢? 

class Date
{
public:
	Date(int year = 2023, int month = 8, int day = 14)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year = 2023, int month = 8, int day = 14)" << endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date* d1 = new Date[10];
	free(d1); //delete d1;  运行错误
}

错误:

【C++精华铺】7.C++内存管理,C++,c++,开发语言

        其实告诉大家,这里报错其实是因为free的位置不对,为什么呢,因为这里编译器其实对new T[]做了处理:

【C++精华铺】7.C++内存管理,C++,c++,开发语言

        由于不同编译器的策略可能有不同,所以我们在使用的时候一定要配套使用,不要去交叉。 

2.3  定位new表达式(了解)

        定位new表达式区别于new的地方就是定位new不开空间,而是对一块已经存在的空间进行初始化。定位new的使用场景比较少,一般会与内存池一起搭配使用。

使用格式:
new (place_address) type或者new (place_address) type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表

class Date
{
public:
	Date(int year = 2023, int month = 8, int day = 14)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year = 2023, int month = 8, int day = 14)" << endl;
	}
	void print()
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	int* p = (int*)malloc(sizeof(int));
	new(p)int(1);
	cout << *p << endl;

	cout << "-----------------------------" << endl;

	Date* d = (Date*)malloc(sizeof(Date));
	new(d)Date(2023, 8, 15);
	d->print();
}

输出:

【C++精华铺】7.C++内存管理,C++,c++,开发语言

 补充:内存池机制

        内存池是从堆中划出来的空间,正常我们向堆中申请空间需要先向操作系统申请,再由操作系统给我们分配,而向内存池申请不需要与操作系统交互,而是直接由内存池分配返回地址,因此内存池效率很高。

【C++精华铺】7.C++内存管理,C++,c++,开发语言

 3. C语言 malloc/free和C++ new/delete的区别(面试常考)

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:文章来源地址https://www.toymoban.com/news/detail-651482.html

  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++精华铺】7.C++内存管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++内存管理(3)——内存池

    利用默认的内存管理操作符 new/delete 和函数 malloc()/free() 在堆上分配和释放内存会有一些额外的开销。 系统在接收到分配一定大小内存的请求时,首先查找内部维护的内存空闲块表,并且需要根据一定的算法(例如分配最先找到的不小于申请大小的内存块给请求者,或者分配

    2024年02月09日
    浏览(27)
  • C++——内存分配与动态内存管理

    🌸作者简介: 花想云 ,在读本科生一枚,致力于 C/C++、Linux 学习。 🌸 本文收录于 C++系列 ,本专栏主要内容为 C++ 初阶、C++ 进阶、STL 详解等,专为大学生打造全套 C++ 学习教程,持续更新! 🌸 相关专栏推荐: C语言初阶系列 、 C语言进阶系列 、 数据结构与算法 本章我们

    2023年04月17日
    浏览(44)
  • 【C++】C++内存管理,模板

    C++中通过 new 和 delete 两个操作符实现动态内存管理 为自定义类型设计,可以在申请空间得同时初始化 使用 new 先申请空间,再调用构造 使用 delete 时先调用析构,再释放空间 operator new 和 operator delete 是系统提供的全局函数,不是运算符重载, 在使用 new 或 delete 时,底层实

    2024年02月01日
    浏览(27)
  • c++ 内存管理一:初识内存分配工具

    前言 侯捷 c++内存管理学习总结笔记。 在C++中,有几种常用的内存分配工具可以帮助进行动态内存管理。 从c++应用程序自上而下,通常会有这样的几种分配内存的方式,当然最终都是直接或间接的调用系统的API。 1 new 和 delete new 和 delete :new操作符用于在堆上分配内存,de

    2024年02月11日
    浏览(31)
  • 「探索C语言内存:动态内存管理解析」

    🌠先赞后看,不足指正!🌠 🎈这将对我有很大的帮助!🎈 📝所属专栏:C语言知识 📝阿哇旭的主页:Awas-Home page 目录   引言 1. 静态内存 2. 动态内存 2.1 动态内存开辟函数 2.1.1 malloc函数 2.1.2 calloc函数 2.1.3 realloc函数 2.2 动态内存释放函数 2.2.1 free函数 3. 动态内存的常见

    2024年04月28日
    浏览(27)
  • C语言 — 动态内存管理(动态内存函数)

    本期分为三篇介绍动态内存管理相关内容,关注博主了解更多 博主博客链接:https://blog.csdn.net/m0_74014525 本期介绍动态内存函数,函数如何使用、函数格式、在使用在所需要的注意点及C/C++程序的内存开辟区域 第一篇:C语言 — 动态内存管理(动态内存函数) 第二篇:C语言

    2024年02月14日
    浏览(35)
  • 【C++初阶】七、内存管理(C/C++内存分布、C++内存管理方式、operator new / delete 函数、定位new表达式)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【C++初阶】六、类和对象(初始化列表、static成员、友元、内部类)-CSDN博客  ==================

    2024年02月05日
    浏览(31)
  • C++:内存管理

    首先我们需要了解的是C/C++中内存区域的划分: 1. 栈又叫堆栈 --非静态局部变量/函数参数/返回值等等,栈是向下增长的:先调用的地址比后调用的地址大。 2. 内存映射段是高效的I/O映射方式 ,用于装载一个共享的动态内存库。用户可使用系统接口 创建共享共享内存,做进

    2024年02月05日
    浏览(22)
  • C++进程、线程、内存管理

    目录 进程和线程区别 进程和线程切换的区别 系统调用流程 系统调用是否会引起线程切换 为什么需要使用虚拟内存 本质区别: 进程是资源分配的基本单元。 线程是操作系统调度的基本单元。 地址空间: 进程具有独立的虚拟地址空间。 线程共享进程的虚拟地址空间。 内存

    2024年02月10日
    浏览(29)
  • C++——内存管理+模块

    作者:几冬雪来  时间:2023年5月19日 内容:C++——内存管理+模块 目录 前言:   1.new和delete操作自定义类型:  operator new/delete:  定位new表达式(placement-new):  malloc/free和new/delete区别: 2.模板:  结尾:  在上一篇博客中我们成功的对我们的类和对象的知识进行了收尾

    2024年02月06日
    浏览(20)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包