草庐IT

【C++从入门到放弃】C/C++内存管理(new和delete的用法详解)

情话0.0 2023-09-08 原文

🧑‍💻作者: @情话0.0
📝专栏:《C++从入门到放弃》
👦个人简介:一名双非编程菜鸟,在这里分享自己的编程学习笔记,欢迎大家的指正与点赞,谢谢!

C/C++内存管理


前言

在学习C/C++内存管理之前,我们先看一下下面的代码与相关问题。

int globalVar = 1;

static int staticGlobalVar = 1;

void Test()
{
 static int staticVar = 1;
 int localVar = 1;
 int num1[10] = { 1, 2, 3, 4 };
 char char2[] = "abcd";
 const char* pChar3 = "abcd";
 int* ptr1 = (int*)malloc(sizeof(int) * 4);
 int* ptr2 = (int*)calloc(4, sizeof(int));
 int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
 free(ptr1);
 free(ptr3);
}
  1. 选择题:
    选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
    globalVar在哪里?____ staticGlobalVar在哪里?____
    staticVar在哪里?____ localVar在哪里?____
    num1 在哪里?____
    char2在哪里?____ *char2在哪里?___
    pChar3在哪里?____ *pChar3在哪里?____
    ptr1在哪里?____ *ptr1在哪里?____

  2. 填空题:
    sizeof(num1) = ____;
    sizeof(char2) = ____; strlen(char2) = ____;
    sizeof(pChar3) = ____; strlen(pChar3) = ____;
    sizeof(ptr1) = ____;

  1. 栈又叫堆栈——非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
  3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
  4. 数据段——存储全局数据和静态数据。
  5. 代码段——可执行的代码/只读常量。

一、C语言中的动态内存管理方式

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

int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
//在这里不用需要free(p2),这是因为p3是要申请40个字节的空间,若p2所指向的空间足够的话那么就是把p2改成了p3(换了名字)
//若内存空间不足时就会重新找一块空间,同时编译器会自动将原来的空间释放掉
free(p3 );

关于C语言的动态内存管理记不太清楚的话可以先看一下这篇文章:链接: 戳一下

二、C++动态内存管理

  在C++中,C语言的内存管理方式还可以继续使用,但是面对一些场景就不太友好了,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

  假设我们现在要在堆上建立一个Test类的对象:

class Test
{
public:
	Test()
	{
		cout << "Test():" << this << endl;;
	}
	~Test()
	{
		cout << "~Test():" << this << endl;
	}
private:
	int a;
};
int main()
{
 	Tset* p = (Test*)malloc(sizeof (Test));
 	free(p);
	return 0;
}

  在我们调试之后发现 p 指向了一段空间,说明该对象的空间已经申请好,然而在我们运行完之后发现 p 所指向那段空间并不是Test类的对象,为什么这样说呢,因为它并没有调用构造函数,它只是在堆上开辟了一块与Test大小相同的内存空间。(free函数也没有调用析构函数进行释放内存空间)所以说,为了能够调用构造函数完成自定义类型的对象的创建以及该对象的销毁,我们就得使用下面的方法:new/delete

1. new/delete操作内置类型

int* p1 = new int;//动态申请一个int类型的空间
delete p1;
		
int* p2 = new int(10);//动态申请一个int类型的空间并初始化为10
delete p2;
		
int* p3 = new int[10];//动态申请十个int类型的空间
delete[] p3;
		
int* p4 = new int[10]{0};//动态申请十个int类型的空间并都初始化为0
delete[] p4;
		
int* p5 = new int[10]{0,1,2,3,4,5,6,7,8,9};//将十个int类型空间初始化为这10个数字
delete[] p5;

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]

2. new和delete操作自定义类型

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}

private:
	int _a;
};

int main()
{
 	// new/delete 和 malloc/free最大区别是 new/delete对于自定义类型除了开空间,还会调用构造函数和析构函数
	A* p1 = (A*)malloc(sizeof(A));
	A* p2 = new A(1);
	free(p1);
	delete p2;
	cout << "_____________________" << endl;
	
	// 内置类型是几乎是一样的
	int* p3 = (int*)malloc(sizeof(int)); // C
	int* p4 = new int;
	free(p3);
	delete p4;
	cout << "_____________________" << endl;
	
	A* p5 = (A*)malloc(sizeof(A)*10);
	A* p6 = new A[10];
	free(p5);
	delete[] p6;
	return 0;
}

注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。在使用内置类型对象时可以进行初始化,在使用自定义类型对象的连续空间时则不可进行初始化,它只能使用无参的或全缺省参数的构造函数。

3. malloc和free,new和delete,new[]和delete[]的匹配使用

  malloc和free,new和delete,new[]和delete[]必须匹配使用,否则会导致内存泄漏或程序崩溃。

  1.对于内置类型的空间没有匹配使用,没有任何影响,但是最好不要这样去写,这样是一种非常不好的习惯。
  2.自定义类型:

class Test
{
public:
	Test()
	{
		b = new char;
		cout << "Test():" << this << endl;;
	}
	~Test()
	{
		delete b;
		cout << "~Test():" << this << endl;
	}
private:
	int a;
	char* b;
};

void Testfunc()
{
	Test* p1 = (Test*)malloc(sizeof(Test));
	delete p1;
}

int main()
{
	Testfunc();
	return 0;
}

  对于上述的这种匹配方法中,我们用到malloc申请空间,然而他并没有调用构造函数申请对象,只是申请了一段与Test类相等大小的空间,当后面使用delete来进行释放空间时,它会调用析构函数,在析构函数中会对对象 b 指针所指向的空间进行释放,而对象 b 没有被初始化,它只是个随机值,所以就会导致程序发生崩溃。

class Test
{
public:
	Test()a(1)
	{
		b = new char;
		cout << "Test():" << this << endl;;
	}
	~Test()
	{
		delete b;
		cout << "~Test():" << this << endl;
	}
private:
	int a;
	char* b;
};

void Testfunc()
{
	Test* p2 = new Test;
	free(p2);
}

int main()
{
	Testfunc();
	return 0;
}



  对于这种匹配类型,使用new再栈帧上申请出了一个对象 p2,调用构造函数,在堆上完成 a 和 b 指针的初始化,b 指针又在堆上申请一段空间存放 char 类型的数据,然后调用 free 函数,在这里 free 函数直接将 p2 所指向堆上的空间销毁掉了,但是并没有把 b 指针所指向的空间销毁掉,这样就导致了内存泄漏。

4. operator new与operator delete函数

  new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

/*
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 实际也是通过封装了malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。而对于operator new[]则是先调用operator new ,再调用 malloc 函数。operator delete 最终是通过free来释放空间的。

5. new和delete的实现原理

5.1 内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

5.2 自定义类型

new的原理
调用operator new函数申请空间
在申请的空间上执行构造函数,完成对象的构造

delete的原理
在空间上执行析构函数,完成对象中资源的清理工作
调用operator delete函数释放对象的空间

new T[N]的原理
调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
在申请的空间上执行N次构造函数

delete[]的原理
在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

6. malloc/free和new/delete的区别

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

用法:

malloc和free是函数,new和delete是操作符
malloc申请的空间不会初始化,new可以初始化
malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可
malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型

底层:

malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new会抛异常
申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

7. 定位new

关于定位 new 的使用场景是将一块已申请好但未初始化的空间进行初始化或者说就是调用构造函数完成一个对象的初始化。
比如:

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}

private:
	int _a;
};
int main()
{
	A* p1=(A*)malloc(sizeof(A));
	if(p1==nulptr)
	{
		perror("malloc fail");
	}
	//定位new
	//new(p1)A;
	new(p1)A(1);//若构造函数有参数,就得传参
	p1->~A();
 	free(p1);

	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);
 	p2->~A();
	operator delete(p2);
	return 0;
}

  对于这种定位new的使用场景有些人就表示疑惑,为什么要先开辟一块未初始化的空间,然后再使用该方法完成初始化呢?为什么不直接new和delete一块使用在开辟空间时就完成对象的初始化呢?
  答案就是定位new的使用场景并不在此,而是为了提升性能,当你频繁申请空间时就不在堆上申请了,而是在一个名叫内存池的地方申请,而内存池的空间都是未初始化的,所以就得使用定位new完成初始化,这样做就避免了频繁向堆申请空间,直接从内存池上拿空间。内存池上的空间其实还是来自于堆上。

总结

学了C++的动态内存的相关知识,一定明白new和delete的实现原理和malloc/free的区别。

有关【C++从入门到放弃】C/C++内存管理(new和delete的用法详解)的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. ruby - i18n Assets 管理/翻译 UI - 2

    我正在使用i18n从头开始​​构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在ruby​​onrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi

  3. ruby-on-rails - Enumerator.new 如何处理已通过的 block ? - 2

    我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m

  4. ruby-on-rails - Ruby 中的内存模型 - 2

    ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序

  5. ruby-on-rails - 获取 inf-ruby 以使用 ruby​​ 版本管理器 (rvm) - 2

    我安装了ruby​​版本管理器,并将RVM安装的ruby​​实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby​​。有没有办法让emacs像shell一样尊重ruby​​的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el

  6. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

  7. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  8. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  9. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  10. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

随机推荐