请回答C++【C/C++内存管理】

本文深入探讨了C/C++内存管理,包括内存划分(栈、堆、数据段、代码段)、动态内存管理(malloc、calloc、realloc、free与new、delete的区别)、自定义类型内存管理、operatornew与operatordelete的作用,以及定位new表达式的应用。特别强调了内存泄漏的危害及避免策略,提供了实例解析malloc/free与new/delete的差异,并讨论了智能指针在防止内存泄漏中的作用。

BingWallpaper3

image-20220213135222319

1. C/C++内存分布

1.1 内存划分

image-20220209124558986

🍁 栈__又叫__堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的。栈一般是有规定大小,Linux下栈是8M

🍁 __内存映射段__是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。

🍁 __堆__用于程序运行时动态内存分配,堆是可以上增长的。堆一般比较大,一般是2G的空间

🍁 数据段–存储全局数据和静态数据。

🍁 代码段–可执行的代码/只读常量。⚠️const变量不是在常量区

1.1.1 栈的演示

向下生长就是b的地址要比a的地址小

void f2()
{
	int b = 0;
	cout << "b:" << &b << endl;
}
void f1()
{
	int a = 0;
	cout <<"a:" <<&a << endl;
	f2();
}
int main()
{
	f1();
	return 0;
}

image-20220209124736689

1.1.2 堆的演示

int main()
{
		int* p1 = (int*)malloc(4);
		int* p2 = (int*)malloc(4);
		cout << "p1:" << p1 << endl;
		cout << "p2:" << p2 << endl;
		free(p1);
	}

image-20220209131037415

1.2 🌰

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

🌿 选择题:
选项 : A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)

globalVar在哪里?staticGlobalVar在哪里?staticVar在哪里?localVar在哪里?num1 在哪里?
CCCAA
char2在哪里?* char2在哪里?pChar3在哪里?* pChar3在哪里?ptr1在哪里?* ptr1在哪里?
AAAD(地址在常量区)AB(只有*ptr1才会在堆上)

🌿 填空题:(单位/字节)

sizeof(num1) = ;sizeof(ptr1) = ;404
sizeof(char2) = ; strlen(char2) = ;54
sizeof(pChar3) = ; strlen(pChar3) = ;4/84

2. C语言动态内存管理

主要涉及malloc/calloc/realloc和free四个函数

void Test ()
{
	int* p1 = (int*) malloc(sizeof(int));
	free(p1);
	// 1.malloc/calloc/realloc的区别是什么?
	int* p2 = (int*)calloc(4, sizeof (int));
	int* p3 = (int*)realloc(p2, sizeof(int)*10);
	// 这里需要free(p2)吗?
	free(p3 );
}

看看三个函数的区别

image-20220209134846049

3. C++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

3.1 针对内置类型

new/delete和malloc/free 针对内置类型没有任何差别,只是用法不一样

int main()
{
	// 申请一个10个int的数组
	int* p1 = (int*)malloc(sizeof(int) * 10);
	int* p2 = new int[10];

	free(p1);
	delete[] p2;

	// 申请单个int对象
	int* p3 = (int*)malloc(sizeof(int));
	int* p4 = new int;

	free(p3);
	delete p4;

	return 0;
}

image-20220211193018737

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

3.2 针对自定义类型

struct ListNode
{
	//struct ListNode* _next; //C
	ListNode* _next;
	ListNode* _prev;
	int _val;
	ListNode(int val = 0)
		:_next(nullptr)
		, _prev(nullptr)
		, _val(val)
	{
		cout << "ListNode(int val = 0)" << endl;
	}
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};	

int main()
{
    // C languege malloc只是开空间
	//free释放空间
	struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
	free(n1);

	// cpp new 针对自定义类型,开空间+构造函数初始化
	//     delete 针对自定义类型,析构函数清理 + 释放空间
	ListNode* n2 = new ListNode(5);   // -> 相当于c语言中BuyListNode(5) 
	delete n2;
}	

如果new和delete类型不匹配,会主动提示报错

image-20220211194022298

另外C++11有一个初始化写法

int* p2 = new int[4]{ 1, 2, 3, 4 };

小结:

image-20220211195732005

在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会

4. operator new与operator delete

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

4.1 malloc和operator new的区别

用法跟malloc和free是完全一样的,功能都是在堆上申请释放空间

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

	ListNode* p2 = (ListNode*)operator new(sizeof(ListNode));
	operator delete(p2);

失败了处理方式不一样,malloc失败返回NULL,operator new失败以后抛异常

//malloc
void* p3 = malloc(0xefffffff);
	if (p3 == NULL){
		cout << "malloc fail" << endl;
	}
//new
	try{
		void* p4 = operator new(0xefffffff);
	}
	catch (exception& e){
		cout << e.what() << endl;
	}

image-20220212212315897

也就是说当我们使用new时,发生了如下的封装的函数的调用

image-20220212211451243

4.2 operator new与operator delete的类专属重载

针对链表的节点ListNode通过__重载类专属 operator new/ operator delete__,实现链表节点__使用内存池申请和释放内存,提高效率__。

	struct ListNode
	{
		ListNode* _next;
		ListNode* _prev;
		int _data;
		void* operator new(size_t n)
		{
			void* p = nullptr;
			p = allocator<ListNode>().allocate(1);//STL中的空间配置器,也就是一个简单的内存池
			cout << "memory pool allocate" << endl;
			return p;
		}
		void operator delete(void* p)
		{
			allocator<ListNode>().deallocate((ListNode*)p, 1);
			cout << "memory pool deallocate" << endl;
		}
	};
	class List
	{
	public:
		List()
		{
			_head = new ListNode;
			_head->_next = _head;
			_head->_prev = _head;
		}
		~List()
		{
			ListNode* cur = _head->_next;
			while (cur != _head)
			{
				ListNode* next = cur->_next;
				delete cur;
				cur = next;
			}
			delete _head;
			_head = nullptr;
		}
	private:
		ListNode* _head;
	};

如果对new类专属重载的话相当于不再是像系统申请一块空间,而是特定的ListNode域申请空间,类比于你受到压岁钱,重载相当于放到了银行里,本质虽然都是来源于长辈给的压岁钱,但是一个是全局的,一个是限定类专属的

image-20220212220343023

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. 定位new表达式(placement-new)

该表达式想要解决的问题是__在已分配的原始内存空间中调用构造函数初始化一个对象__

6.1 使用方法

new (place_address) type / new (place_address) type(initializer-list)

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

6.2 使用示例(使用情况不多)

	// 等价于直接用A* p = new A
	A* p = (A*)operator new(sizeof(A));
	new(p)A; // new(p)A(3);  // 定位new,placement-new,显示调用构造函数初始化这块对象空间

	// 等于 delete p
	p->~A(); // 析构函数可以显示调用,构造函数不可以
	operator delete(p);

7. 一波🌰即将来袭

7.1 🌰malloc/free和new/delete的区别

7.1.1共同点

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

7.1.2 不同点

🍁 malloc和free是__函数__,new和delete是__操作符__

🍁 malloc申请的空间不会初始化,new__可以初始化__

🍁 malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可

🍁 malloc的返回值为void*, 在__使用时必须强转__,new不需要,因为new后跟的是空间的类型

🍁 malloc申请空间失败时,返回的是NULL,因此__使用时必须判空__,new不需要,但是__new需要捕获异常__

🍁 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后__会调用构造函数完成对象的初始化__,delete在释放空间前会调用析构函数完成空间中资源的清理

7.2 🌰🌰内存泄漏

内存泄漏是指针丢了还是内存丢了?

答:指针丢了,申请完空间用了之后不归还,典型的占着茅坑不拉屎

void MemoryLeaks()
{
	// 1.内存申请了忘记释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;
	// 2.异常安全问题
	int* p3 = new int[10];
	Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
	delete[] p3;
}
7.2.1 什么是内存泄漏

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

7.2.2 内存泄漏的危害

​ 🌸 长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

有些股票交易软件如果内存泄漏严重,突然卡死,会导致出现金融事故

7.2.3 如何避免内存泄漏

🍁 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。但是这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。

🍁 采用RAII思想或者智能指针来管理资源。

🍁 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。

🍁 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

小结:内存泄漏非常常见,解决方案分为两种:

🍁 事前预防型。如智能指针等。
🍁 事后查错型。如泄漏检测工具。

C/C++内存管理有关知识点告一段落,干净又卫生兄弟们

### 回答1: c/c++和易语言都是常用的编程语言,可以用来编写驱动内存无痕读写的源码。 1. C/C++的驱动内存无痕读写源码: C/C++可以使用Windows提供的驱动开发工具包(Windows Driver Kit,简称WDK)编写驱动程序。在编写驱动程序时,可以使用设备驱动程序接口(Device Driver Interface,简称DDI)函数来读写内存。 首先,需要创建一个设备驱动程序,定义驱动程序的入口点,在其中初始化驱动程序,并注册读写内存的回调函数。在回调函数中,可以使用DDI函数来访问和修改内存。 2. 易语言的驱动内存无痕读写源码: 在易语言中,可以使用EasyAnti提供的驱动内存读写接口来实现驱动内存无痕读写的功能。EasyAnti是一个易语言开发的驱动内核模块,提供了一系列的读写内存函数。 首先,需要加载EasyAnti驱动模块,并进行初始化。然后,使用EasyAnti提供的读写内存函数来访问和修改内存。 无论是使用C/C++还是易语言,驱动内存无痕读写都需要特殊的权限和操作系统支持。此外,在进行驱动内存无痕读写时,需要确保程序安全可靠,避免对系统造成损害。 ### 回答2: C/C++和易语言都是编程语言,可以用来编写驱动内存无痕读写的源码。 驱动内存无痕读写是指在操作系统内核层面进行内存读写操作,不留下任何痕迹,不受应用程序或者防护软件的检测和干扰。 在C/C++中,可以使用Windows内核编程技术来实现驱动内存无痕读写。具体步骤如下: 1. 创建一个内核模式的驱动程序,可以使用Visual Studio等开发工具。 2. 在驱动程序中,使用操作系统提供的API函数来打开、读取和写入进程的内存。 3. 在驱动程序中,通过提权来获得对内核空间的访问权限。 4. 使用内核模式的I/O函数来读取和写入指定进程的内存。 在易语言中,可以使用Win32扩展库来实现驱动内存无痕读写。具体步骤如下: 1. 创建一个易语言项目,导入Win32扩展库。 2. 使用Win32扩展库提供的函数和接口,来打开、读取和写入进程的内存。 3. 通过调用Windows API函数来获得对内核空间的访问权限。 4. 使用Win32扩展库提供的函数来读取和写入指定进程的内存。 无论是使用C/C++还是易语言,实现驱动内存无痕读写都需要对操作系统内核有一定的了解,并且需要具备驱动开发和内核编程的相关知识和技能。此外,如此高级的技术也可能涉及到系统安全和法律合规性的问题,需慎重使用。 ### 回答3: C/C++和易语言都是常用的编程语言,可以用于编写驱动程序和进行内存的读写操作。下面分别讨论这两种语言的驱动内存无痕读写源码实现。 对于C/C++语言来说,可以通过直接调用操作系统提供的API函数来实现驱动内存的无痕读写。例如,在Windows平台上,可以使用Windows内核编程接口(Kernel-Mode Driver Framework,简称KMDF)和Windows驱动开发包(Windows Driver Kit,简称WDK)来编写驱动程序。具体实现的步骤包括以下几个方面: 1. 首先,需要通过注册驱动程序等步骤将编写的驱动程序加载到操作系统中。 2. 然后,驱动程序使用C/C++语言编写,并通过调用API函数来获取目标进程的句柄,进而访问进程的内存。 3. 在获取目标进程的内存句柄之后,可以通过调用API函数读取或写入目标进程的内存数据。 相比之下,易语言编写驱动程序则较为简单。由于易语言的语法和开发方式相对于C/C++来说更加简洁,易于上手和理解,因此许多初学者也会选择使用易语言编写驱动程序。易语言的编程环境自带了驱动开发模块,可以直接在集成开发环境中编写驱动程序,无需独立的驱动开发包。然而,易语言编写驱动程序的功能和灵活性相对较弱,对于一些高级操作可能存在限制。 综上所述,C/C++和易语言都可以用于编写驱动程序和进行内存的无痕读写操作。相比而言,C/C++提供了更为底层、灵活和功能丰富的编程能力,适合于对性能和功能有较高要求的应用场景;而易语言则更适合初学者或对功能需求较简单的场景。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

言之命至9012

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值