C++ 内存管理 —— 第一講:C++ 內存構件

C++ 内存管理
侯捷老师的课程内容,做了个简单的记录

第一講:C++ 內存構件

四个层面

C++应用程序,使用memory的途径
SGI STL G2.9 用的allocate (SGI) 是直接调用的malloc(),图有些差错
image-20211201154834619

C++ memory primitives

分配释放属性是否重载
malloc()free()C函数不可
newdeleteC++表达式不可
::operator new()::operator delete()C++函数
alloctor<T> allocate()alloctor<T> deallocate()C++标准库可自由设计并以之搭配任何容器

基本構件 new delete expression

new expression

image-20211201160822819

delete expression

image-20211201161837583

malloc的cookie机制

a328c7dbc9d96a0c126c491bbbabe0ac.png

在malloc为用户分配内存的时候,除了分配用户本身的内存,还会在内存前后加上两个cookie,来记录分配了多少内存,这样在调用free函数的时候才能准确的回收内存。因此每次调用malloc函数都会产生cookie消耗。

而new操作符中的array new,为了记录需要调用多少次析构函数,会在分配的内存前记录分配了多少个对象。下面的图更好理解一点:

malloc和new的内存分布布局
上图展示了我们在运行代码示例一之后分配的内存,其中61H是malloc设置的cookie,表示malloc总共分配给用户的内存大小;而00481c30地址所指的3,则是new设置的其分配对象的个数。也就是说,每次调用new操作符的时候,都会分配一些额外的内存来存放所分配内存信息。

arrary new、arrary delete

对于 Complex 是POD类型, delete 调用3次和1次没区别
string类 delete调用少了,会发生泄漏

image-20211201161959530

image-20211201162944495

operator new[]和operator delete[]

首先来看下面这段代码:

// 代码示例二
#include <iostream>

using namespace std;

class Demo
{
private:
    int a, b, c;

public:
    Demo() : a(0), b(0), c(0) 
    {
        cout << "constructor" << endl;
    }
    ~Demo() 
    {
        cout << "destructor" << endl;
    }
};

int main()
{
    Demo *p = new Demo[3];
    delete[] p;
    return 0;
}
/*
输出:
constructor
constructor
constructor
destructor
destructor
destructor
*/

上面的代码示例二展示了array new和array delete的用法,可以看到,其中调用了3次构造函数和3次析构函数。如果我们将delete[] p改为delete p,结果会是什么样呢?

将delete[]改为delete
将delete[] p改为delete p之后,会出现上图的错误,invalid pointer。为什么会出现这种情况呢,前面我们提到过,在使用new操作符分配一个数组时,会在分配的数组前面多分配几个字节(视环境而定侯捷老师说是4个字节,但是在我的环境下面是8个字节),再来看一下前面那个图:

我们在运行了代码示例2之后,实际分配的内存如上图所示(仅仅是为了说明,地址并不准确)。在运行Demo *p =new Demo[3]之后,返回的指针p是0x00481c34,在00481c34之前还有用来存放对象数量的内存,4个字节或者8个字节,图中展示为8个字节。

在调用delete[] p的时候,会调用operator delete[]函数,而传入operator delete[]函数的指针其实是从0x00481c30开始的,并不是从对象真正的地址开始,因为new[]申请的内存是从0x00481c30开始的。

placement new —— 定位new

  • placement new允群我们将object建媾於 allocated memory 中。
  • 没有所谓 placement delete﹐因爲placement new根本没分配memory,亦或称呼 舆placement new 对应的operator delete爲 placement delete.

注意﹐開於“placement new”,
或指new§
或指::operator new(size, void*)

image-20211201164433381

重载

C++ 应用程序,分配内存的途径

image-20211201164951925

C++ 容器,分配内存的途径

image-20211201165126895

重载 new() / delete()

我们可以重截class member operator new()﹐寓出多佃版本﹐前提是每一版本的馨明都必须有嗳特的参敷列﹐其中第一参数必须是size_t,其余参数以new所指定的placement arguments爲初值·出现於 new …小括虢内的便是所谓placement arguments 。

我们也可以重戴class member operator delete(),写出多个版本·但它绝不会被delete 调用。只有常new所调用的ctor抛出exception,才会调用这些重载的operator delete()·它只可能被这样调用﹐主要用来归还未能完全创建成功的 object所占用的memory 。

例子

image-20211201170814051

image-20211201171157177

basic_string 使用new(extra) 扩充申请量

image-20211201171508461

per-class allocator 每个class一个 allocator

想利用类内重载operator new去接管内存的分配,然后利用内存池的观念【即创建出一大段连续空间的内存,然后将其切割成一小段一小段】,将创建的元素对象放在内存池切分好的各分段小内存片中,这样避免了多次调用new而造成生成多个带有cookie的内存空间。通过内存池的观念,可以生成一大段只带有两个头尾cookie的内存空间,而该一大段内存空间又被切分成每一小段的内存空间,且其中的每一小段内存空间片都可以共享这一整体的cookie信息。

per-class allocator 1

per-class allocator 1

  • 因为为了能将一大段内存空间切分成一小段一小段,然后通过单向链表的形式串接起来,所以必须多引入一个Screen* next指针。但这又会增加class Screen的大小【增加了4字节】。

    另外,static Screen* freeStore 和 static const int screenChunk是静态成员变量,是声明class Screen就创建出来的了【而且被其所有类对象共享的】,并不是在创建每一个Screen类对象时才被创建出来,因而并不计算入类对象的大小中【所以生成的每一个类对象其大小为4+4=8】。

#include <cstddef>
#include <iostream>
namespace jj04
{
//ref. C++Primer 3/e, p.765
//per-class allocator 

class Screen {
public:
    Screen(int x) : i(x) { };
    int get() { return i; }

    void* operator new(size_t);
    void  operator delete(void*, size_t);	//(2)
//! void  operator delete(void*);			//(1) 二擇一. 若(1)(2)並存,會有很奇怪的報錯 (摸不著頭緒) 
	    
private:
    Screen* next;
    static Screen* freeStore;
    static const int screenChunk;
private:
    int i;
};
Screen* Screen::freeStore = 0;
const int Screen::screenChunk = 24;

void* Screen::operator new(size_t size)
{
  Screen *p;
  if (!freeStore) {
      //linked list 是空的,所以攫取一大塊 memory
      //以下呼叫的是 global operator new
      size_t chunk = screenChunk * size;
      freeStore = p =
         reinterpret_cast<Screen*>(new char[chunk]);
      //將分配得來的一大塊 memory 當做 linked list 般小塊小塊串接起來
      for (; p != &freeStore[screenChunk-1]; ++p)
          p->next = p+1;
      p->next = 0;
  }
  p = freeStore;
  freeStore = freeStore->next;
  return p;
}


//! void Screen::operator delete(void *p)		//(1)
void Screen::operator delete(void *p, size_t)	//(2)二擇一 
{
  //將 deleted object 收回插入 free list 前端
  (static_cast<Screen*>(p))->next = freeStore;
  freeStore = static_cast<Screen*>(p);
}

//-------------
void test_per_class_allocator_1()
{	
	cout << "\ntest_per_class_allocator_1().......... \n";	
		
   	cout << sizeof(Screen) << endl;		//8	

size_t const N = 100;
Screen* p[N];	

   	for (int i=0; i< N; ++i)
   	     p[i] = new Screen(i);         

	//輸出前 10 個 pointers, 用以比較其間隔 
   	for (int i=0; i< 10; ++i)  	   
		cout << p[i] << endl;     
    
   	for (int i=0; i< N; ++i)
   	     delete p[i];     	
}
} //namespace
-----------------------------------------------

per-class allocator 2

per-class allocator 2

  • 这个版本通过union关键字来减少使用next而所占耗的内存
  • 注意:这两个版本的operator delete操作都没有将内存空间回收还回给系统,而是仍然存在的。虽然operator delete操作没有将这些分配的内存空间释放掉,但其仍在控制中即仍可继续重新使用【只不过freeStore或headOfFreeList此时又重新跑回整个一大块内存空间的头端】,所以不算内存泄漏!
//----------------------------------------------------
#include <cstddef>
#include <iostream>
namespace jj05
{
//ref. Effective C++ 2e, item10
//per-class allocator 

class Airplane {   //支援 customized memory management
private:
  	struct AirplaneRep {
    	unsigned long miles;
    	char type;
  	};
private:
  	union {
    	AirplaneRep rep;  //此針對 used object
    	Airplane* next;   //此針對 free list
  	};
public:
  	unsigned long getMiles() { return rep.miles; }
  	char getType() { return rep.type; }
  	void set(unsigned long m, char t)
  	{
     	rep.miles = m;
     	rep.type = t;
  	}
public:
  	static void* operator new(size_t size);
  	static void  operator delete(void* deadObject, size_t size);
private:
  	static const int BLOCK_SIZE;
  	static Airplane* headOfFreeList;
};

Airplane* Airplane::headOfFreeList;  
const int Airplane::BLOCK_SIZE = 512;   

void* Airplane::operator new(size_t size)
{
  	//如果大小錯誤,轉交給 ::operator new()
  	if (size != sizeof(Airplane))
    	return ::operator new(size);

  	Airplane* p = headOfFreeList;  

  	//如果 p 有效,就把list頭部移往下一個元素
  	if (p)
    	headOfFreeList = p->next;
  	else {
    	//free list 已空。配置一塊夠大記憶體,
    	//令足夠容納 BLOCK_SIZE 個 Airplanes
    	Airplane* newBlock = static_cast<Airplane*>
       		(::operator new(BLOCK_SIZE * sizeof(Airplane)));
    	//組成一個新的 free list:將小區塊串在一起,但跳過 
    	//#0 元素,因為要將它傳回給呼叫者。
    	for (int i = 1; i < BLOCK_SIZE-1; ++i)
      		newBlock[i].next = &newBlock[i+1];
    	newBlock[BLOCK_SIZE-1].next = 0; //以null結束

    	// 將 p 設至頭部,將 headOfFreeList 設至
    	// 下一個可被運用的小區塊。
    	p = newBlock;
    	headOfFreeList = &newBlock[1];
  	}
  	return p;
}

// operator delete 接獲一塊記憶體。
// 如果它的大小正確,就把它加到 free list 的前端
void Airplane::operator delete(void* deadObject,
                               size_t size)
{
  	if (deadObject == 0) return;          
  	if (size != sizeof(Airplane)) {   
    	::operator delete(deadObject);
    	return;
  	}

  	Airplane *carcass =
    	static_cast<Airplane*>(deadObject);

  	carcass->next = headOfFreeList;
  	headOfFreeList = carcass;
}

//-------------
void test_per_class_allocator_2() 
{	
	cout << "\ntest_per_class_allocator_2().......... \n";		
	
  	cout << sizeof(Airplane) << endl;    //8

size_t const N = 100;
Airplane* p[N];	

   	for (int i=0; i< N; ++i)
   	     p[i] = new Airplane;     
			
    
    //隨機測試 object 正常否 
  	p[1]->set(1000,'A'); 	
  	p[5]->set(2000,'B');
  	p[9]->set(500000,'C');
  	cout << p[1] << ' ' << p[1]->getType() << ' ' << p[1]->getMiles() << endl;
  	cout << p[5] << ' ' << p[5]->getType() << ' ' << p[5]->getMiles() << endl;
  	cout << p[9] << ' ' << p[9]->getType() << ' ' << p[9]->getMiles() << endl; 
  	
	//輸出前 10 個 pointers, 用以比較其間隔 
   	for (int i=0; i< 10; ++i)  	   
		cout << p[i] << endl; 		 
	 
   	for (int i=0; i< N; ++i)
   	     delete p[i]; 	
}
} //namespace
//-----

static allocator 3

赏你受困於必须爲不同的classes重写一遍几乎相同的member operator new和 member operator delete时﹐应该有方法将一个总是分配特定尺寸之区块的memory allocator概念包装起来﹐使它容易被重愎使用。以下展示一擂作法﹐每固allocator object都是个分配器﹐它体内维护一个ree-lists ;不同的allocator objects维护不同的free-lists 。

将分配特定尺寸区块的memory allocator包装成一个class allocator,
这样每个allocator object都是个分配器,体内维护一个free lists,
不同类(如下面的class Foo,class Goo等) 里面调用生成的各自allocator objects维护不同的free lists。

另外,class Foo或class Goo中,应注意到:
static allocator myAlloc,即myAlloc必须是个静态成员变量,且在类外定义(赋初值)。如果其不是静态成员变量时,则在构建class Foo类对象时,是没办法调用到myAlloc的。因为非静态成员变量只能通过对象调用【但此时Foo对象还没生成又如何调用!!】。而myAlloc又是用来生成Foo类对象的,所以得通过类名调用即应设置为static类型。

class allocator{
private:
	struct obj{
		struct obj* next;
	};
public:
	static void* allocate(size_t);
	static void deallocate(void*, size_t);
private:
	obj* freeStore = nullptr;
	const int CHUNK = 5; // 标准库一般设置为20
};
void* allocator::allocate(size_t size){
	obj* p;
	if(!freeStore){
		// linked list为空,则申请一大块
		size_t chunk = CHUNK * size;
		freeStore = p = (obj*)malloc(chunk); // 这里直接调用malloc进行分配空间
		
		// 将分配的一大块切分成5小段,并串接起来
		for(int i = 0; i < (CHUNK - 1); ++i){
			p->next = (obj*)((char*)p + size);
			p = p->next;
			// 上面这两步相当于p->next = p + 1,
			// 只不过这里需要适应不同的类下的操作,因而设置成这种形式!!
		}
		p->next = nullptr; // 最后一小段的下一个位置指向空
	}
	p = freeStore;
	freeStore = freeStore->next;
	return p;
}
void allocator::deallocate(void* p, size_t){
	// 将要删除的*p的位置调整为free list的头端
	((obj*)p)->next = freeStore;
	freeStore = (obj*)p;
}

macro for static allocator 4

定义两个宏完成功能
在类内进行宏声明,在类外进行宏定义【告诉编译器传入参数的class type】

image-20211201194118988

// DECLARE_POOL_ALLOC  used in class definition
#define DECLARE_POOL_ALLOC()\
public:\
	void* operator new(size_t size){
		return myAlloc.cllocate(size);
	}\
	void operator delete(void* p){
		myAlloc.deallocate(p, 0);
	}\
protected:\
	static allocator myAlloc;

// IMPLEMENT_POOL_ALLOC   used in class implementation file
#define IMPLEMENT_POOL_ALLOC(class_name)\
	allocator class_name::myAlloc;

new handler

当operator new没能力爲你分配出你所申请的memory,会抛一倜std::bad_alloc exception 。某些老旧的编译器则是返回0,你仍然可以令编译器那么做︰

image-20211201195021927

设计良好的new handler

  • 让更多 memory 可用
  • 调用 abort() 或 exit()

设置 new handler —— set_new_handler

image-20211201195549598

=default , =delete

operator new 不能被default(没有默认版本),可用被delete(禁用)

image-20211201200152649

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值