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.0new一個對象是否爲類對象調用operator new函數C語言的malloc()函數調用類對象的構造函數調用類對象的析構函數調用operator delete函數C語言的free()函數釋放該對象調用operator new函數C語言的malloc()函數調用operator delete函數C語言的free()函數yesno

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也可以通過不同參數表進行重載,但是這是個很獵奇的操作,通常不建議使用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章