C++入门到精通 ——第六章 内存高级话题

六、内存高级话题

Author: XFFer_

01 new、delete的进一步认识

1 总述与回顾
2 从new说起

从new说起

new类对象时加不加括号的区别
class A {
public:
	int m_i;
};
A* pa = new A();	//m_i = 0
A* pa_t = new A;	//m_i = 随机值
  • 当类有成员变量,带有括号的初始化会把一些和成员变量有关的内存清零
  • 当类中有构造函数,则这两种写法得到的结果没有区别
new干了啥

new 可以叫关键字/操作符 fn + F12会跳转到operator new。

可以在Debug状态下使用 调试 -> 窗口 -> 反汇编得到如下:
反汇编
这里可以看出call operator new这个函数
malloc
这里可以看到call mallocC语言中内存分配函数malloc(size传入堆中分配内存的大小)
在这里插入图片描述
这里可以看到call operator delete这个函数

简化流程
Created with Raphaël 2.2.0 new一个对象 是否为类对象 调用operator new函数 C语言的malloc()函数 调用类对象的构造函数 调用类对象的析构函数 调用operator delete函数 C语言的free()函数 释放该对象 调用operator new函数 C语言的malloc()函数 调用operator delete函数 C语言的free()函数 yes no

02 new细节探秘,重载类内operator new、delete

1 new内存分配细节探秘
2 重载类内operator new和operator delete操作符
3 重载类内operator new[]和operator delete[]操作符

new内存分配探秘

先介绍一个函数 void *memset(void *s, int ch, size_t n)
作用是:将某一块内存中的内容全部设定为指定的值。

char* ppchar = new char[10];
memset(ppchar, 0, 10);	//这里将这分配的十字节的内存初始化为0
delete[] ppchar;

内存的回收,实际上并不是只释放了分配的内存,影响的范围很广。给我一种感觉是:最初分配的多个变量的内存并不是连续的,会产生内存碎片,free为了避免碎片导致的内存没有足够的空间储存更大的信息,是一种很复杂的工作。
在这里插入图片描述

重载类内operator new和operator delete操作符

class A {
public:
	static void* operator new(size_t size);
	static void operator delete(void *phead);
};

void* A::operator new(size_t size)
{
	A* ppoint = (A*)malloc(size);
	return ppoint;
}
void A::operator delete(void *phead)
{
	free(phead);
}

A* a = new A();
A* a_c = ::new A();	//::

如果做new前加 :: 全局操作符,那就不会调用自己重载的operator new/delete函数了,而是调用系统内部的。

重载类内operator new[]和operator delete[]操作符

和上一模块的代码很相似,当初始化类对象数组时,只会调用一次operator new[],调用数组长度的构造函数;释放时,先调用数组长度的析构函数,再调用一次operator delete[]。在分配内存时还会多分配4个字节用来储存数组的长度。

03 内存池概念、代码实现和详细分析

1 内存池的概念和实现原理概述
2 针对一个类的内存池实现演示代码
3 内存池代码后续说明

内存池的概念和实现原理概述

malloc:内存浪费,频繁分配小块内存,浪费更加明显。

Q: 内存池要解决的问题?
A: 减少malloc的调用次数。

内存池的实现原理: 用malloc申请一大块内存,当需要分配时,就从该内存池一点点分配给对象。

针对一个类的内存池实现演示代码

class A
{
public:
	static void* operator new(size_t size);
	static void operator delete(void* phead);
	static int m_iCout;	//分配计数统计,每new一次,就统计一次
	static int m_iMallocCount;	//每malloc一次,就统计一次
private:
	A* next;
	static A* m_FreePosi;	//总是指向一块可以分配出去的内存的首地址
	static int m_sTrunkCout;	//一次分配多少倍的该类内存
};
int A::m_iCout = 0;
int A::m_iMallocCount = 0;

A* A::m_FreePosi = nullptr;
int A::m_sTrunkCout = 5;

void* A::operator new(size_t size)
{
	A* tmplink;
	if (m_FreePosi == nullptr)
	{
		size_t relsize = m_sTrunkCout * size;	
		//申请内存池的大小
		m_FreePosi = reinterpret_cast<A*>(new char[relsize]);	
		//传统new,调用系统底层的malloc,char是1字节,这里开辟了relsize个字节的内存空间	
		tmplink = m_FreePosi;

		//把分配出的一大块内存彼此链起来供后续使用
		for (;tmplink != &m_FreePosi[m_sTrunkCout -1]; ++tmplink)
		{
			tmplink->next = tmplink + 1;	
			//这里对tmplink->next的改动,实际上也是对m_FreePosi的改动,进入循环前地址传递
		}
		tmplink->next = nullptr;
		++m_iMallocCount;
	}
	tmplink = m_FreePosi;
	m_FreePosi = m_FreePosi->next;	//把内存池中的下一个指针给m_FreePosi用于下次分配
	++m_iCout;
	return tmplink;
}

void A::operator delete(void *phead)
{
	(static_cast<A*>(phead))->next = m_FreePosi;
	m_FreePosi = static_cast<A*>(phead);
}

内存池
这里介绍一个库 #include <ctime> 单位:ms

  1. clock_t start, end;
  2. start = clock();
  3. end = clock();
  4. cout << "start和end两条语句之间夹着的所有语句的运行时间是:" << end - start << endl;

04 嵌入式指针概念及范例、内存池改进版

1 嵌入式指针
2 内存池代码的改进

嵌入式指针(embedded pointer)

一般应用在内存池中,为了节省下每次指向下个内存池中的内存地址的指针

嵌入式指针工作原理: 借用对象所占用内存空间的前4个字节,这4个字节用来链住空闲的内存块;一旦某一块被分配出去,这4个字节就可以正常使用。
在这里插入图片描述

class Test_Embed {
public:
	int a;
	int b;	//为了凑4字节
	struct obj
	{
		struct obj* next;	//这个next就是个嵌入式指针
	};
};

Test_Embed::obj* ptemp;
Test_Embed test;
ptemp = (Test_Embed::obj*)&test;	//将类对象的首地址也就是前4个字节的首地址给嵌入式指针
ptemp->next = nullptr;	//这里的nullptr用下一个内存块的首地址就可以实现链表

内存池代码的改进

class myallocator
{
public:
	void *allocate(size_t size)
	{
		obj *tmplink;
		if (m_FreePosi == nullptr)	//if内的内容就是在做链接
		{
			size_t realsize = m_sTrunkCout * size;
			m_FreePosi = (obj*)malloc(realsize);	//返回值void*万能指针
			tmplink = m_FreePosi;	//让tmplink这个嵌入式指针指向开辟内存的首地址

			for (int i = 0; i < m_sTrunkCout - 1; ++i)	//一直链接到最后一个内存块
			{
				tmplink->next = (obj*)((char*)tmplink + size);	//指向下一个内存块的首地址
				tmplink = tmplink->next;
			}
			tmplink->next = nullptr;	//最后一个内存块的next指针指向空用来下一次调用if,malloc一个新的内存池
		}
		//现在整个内存池的每个block都已经链接好了,next指针就指向下一个内存块
		templink = m_FreePosi;
		m_FreePosi = m_FreePosi->next;	//跟踪可用内存块
		return tmplink;
	}
	void deallocate(void* phead)
	{
		((obj*)phead)->next = m_FreePosi;
		m_FreePosi = (obj*)phead;
	}
private:
	struct obj
	{
		struct obj* next;
	};
	int m_sTrunkCout = 5;
	obj* m_FreePosi = nullptr;
};

//如何使用
class useit {
public:
	int m_i;
	int m_o;	//这两个纯属为了使类对象>4字节
public:
	static myallocator myalloc;	//在下部定义
	static void* operator new (size_t size)
	{
		return myalloc.allocate(size);	//返回的是一个指针
	}
	static void operator delete (void* tmp)
	{
		myalloc.deallocate(tmp);
	}
};
myallocator useit::myalloc;	//定义静态变量

//用宏简写
#define DECLEAR_POOLING_ALLOC() \
public:\
	static myallocator myalloc;\
	static void* operator new (size_t size)\
	{\
		return myalloc.allocate(size);\
	}\
	static void operator delete (void* tmp)\
	{\
		myalloc.deallocate(tmp);\
	}
#define IMPLEMENT_POOL_ALLOC(classname) \
myallocator classname::myalloc

class A {
	DECLEAR_POOLING_ALLOC()
public:
	int m_j;
	int m_k;
};
IMPLEMENT_POOL_ALLOC(A);

05 重载new、delete,定位new及重载

1 定位new (placement new)
2 多种版本的operator new重载

定位new(placement new)

没有placement delete

功能: 在已经分配的原始内存中初始化一个对象。
格式: new (地址) 类对象()

//定位new也可以重载
void* operator new (size_t size, void* ppoint)
{
	return ppoint;
}

多种版本的operator new重载

new调用了operator new的函数,和其他函数形式一样,operator new也可以通过不同参数表进行重载,但是这是个很猎奇的操作,通常不建议使用。
发布了27 篇原创文章 · 获赞 60 · 访问量 6579

猜你喜欢

转载自blog.csdn.net/qq_44455588/article/details/104423217
今日推荐