Box2d源码学习<三> 内存管理之栈的实现

本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8271251

Box2d上有两个和栈有关的类,它们分别是b2StackAllocator和b2GrowableStack。

B2StackAllocator主要是为了运行一个步长时满足box2d需要的临时内存空间,作为栈分配器来防止单步堆分配。

B2GrowableStack主要是为了满足动态树b2DynaicTree中光线投射和区域查询所需要的临时内存空间,这个到动态树的时候再做详细研究。


下面我们就来看这两个类是如何实现的。

1、 b2StackAllocator类


首先我们看它头文件b2StackAllocator.h

看下相关常量和结构体的定义:

//栈中内存池的大小
const int32 b2_stackSize = 100 * 1024;	// 100k
//栈元素的最大数量
const int32 b2_maxStackEntries = 32;
//栈实体定义
struct b2StackEntry
{
	char* data;          //头指针
	int32 size;          //大小
	bool usedMalloc;     //是否使用
};

代码上面有注释,不多说了。

下面看看b2StackAllocator类的定义吧(好期待(⊙o⊙)哦)

// 这是一个栈分配器用于每一步都快速的分配
// 你必须成对使用allocate/free这对函数。
// 如果你使用allocate/free次数不同时,将会出现断言。
class b2StackAllocator
{
public:
	
	/**************************************************************************
	* 功能描述:构造函数
	* 参数说明:(void)
	* 返 回 值:无
	**************************************************************************/
	b2StackAllocator();
	/**************************************************************************
	* 功能描述:析构函数
	* 参数说明:(void)
	* 返 回 值:无
	**************************************************************************/
	~b2StackAllocator();
	/**************************************************************************
	* 功能描述:申请内存函数
	* 参数说明:size :需要申请的内存大小
	* 返 回 值:申请的内存头指针
	**************************************************************************/
	void* Allocate(int32 size);
	/**************************************************************************
	* 功能描述:释放内存函数
	* 参数说明:p    :释放内存的头指针
	* 返 回 值:(void)
	**************************************************************************/
	void Free(void* p);
	/**************************************************************************
	* 功能描述:获取已申请的内存容量的最大值
	* 参数说明:(void)
	* 返 回 值:曾经进栈过所有元素【不管现在是否出栈】使用的内存容量
	**************************************************************************/
	int32 GetMaxAllocation() const;

private:
	//栈的内存池,用于栈子节点的内存开辟
	char m_data[b2_stackSize];
	//在栈的内存池中,已使用的内存大小
	int32 m_index;
	//栈中的所有元素使用内存大小
	int32 m_allocation;
	//曾经进栈过所有元素【不管现在是否出栈】使用的内存容量
	//注意该变量在对象销毁之前只增不减
	int32 m_maxAllocation;
	//栈实体的数组
	b2StackEntry m_entries[b2_maxStackEntries];
	//栈中元素的数量
	int32 m_entryCount;
};

同样不多说了,看注释。我们再来看看b2StakAllocator是如何实现的:

b2StackAllocator::b2StackAllocator()
{
    //初始化相关变量
	m_index = 0;
	m_allocation = 0;
	m_maxAllocation = 0;
	m_entryCount = 0;
}
b2StackAllocator::~b2StackAllocator()
{
	//验证内存是否已完全释放
	b2Assert(m_index == 0);
	//验证元素是否已完全出栈
	b2Assert(m_entryCount == 0);
}

下面是Allocate、Free、GetMaxAllocation函数,看代码:

void* b2StackAllocator::Allocate(int32 size)
{
	//验证栈中元素的有效性,防止内存溢出
	b2Assert(m_entryCount < b2_maxStackEntries);
	//获取栈实体头指针
	b2StackEntry* entry = m_entries + m_entryCount;
	//实体大小
	entry->size = size;
	//当内存池m_data已使用的大小加需要申请的大小大于内存池的总容量时,则在堆上申请
	if (m_index + size > b2_stackSize)
	{
		//申请大小为size的内存,并标记是在堆上申请的
		entry->data = (char*)b2Alloc(size);
		entry->usedMalloc = true;
	}
	else
	{
		//从m_data中获取内存,并标记不是在堆上申请的
		//同时修改m_index的值
		entry->data = m_data + m_index;
		entry->usedMalloc = false;
		m_index += size;
	}
	//增加栈中的所有元素使用内存大小
	m_allocation += size;
	//修改内存容量的最大值
	m_maxAllocation = b2Max(m_maxAllocation, m_allocation);
	//增加栈中元素的数量
	++m_entryCount;
	//返回栈中元素的内存头指针
	return entry->data;
}

void b2StackAllocator::Free(void* p)
{
	//验证栈中元素的有效性
	b2Assert(m_entryCount > 0);
	//栈中数量减1,在这里用数组模拟了出栈
	b2StackEntry* entry = m_entries + m_entryCount - 1;
	b2Assert(p == entry->data);
	//是否是在堆上申请的
	if (entry->usedMalloc)
	{
		//释放p
		b2Free(p);
	}
	else
	{
		//将索引值减去栈实体的内存大小
		m_index -= entry->size;
	}
   //减去已释放的内存大小
	m_allocation -= entry->size;
    //元素数量减少
	--m_entryCount;
    //将指针置空,防止野指针
	p = NULL;
}
int32 b2StackAllocator::GetMaxAllocation() const
{
	return m_maxAllocation;
}

对于Allocate函数,我们用原先分配了大小b2_stackSize的内存池辅助数组。同时也用m_index记录了已使用的内存池的大小。若内存池中剩余内存不够则在堆上申请,否则在内存池中获取相应大小的内存。

同样Free函数也有两种情况,在堆上申请的内存直接释放,在内存池中申请的内存返还到内存池中,不用释放。

GetMaxAllocation函数在同一个对象的生命周期里是返回的值是只增不减的。

 

2、 b2GrowableStack类

本类定义、实现均在b2GrowableStack.h文件中

//这是一个可增长的先进后出初始容量为N的栈
//如果栈的大小达到初始容量,则在堆中增加栈的大小
template <typename T, int32 N>
class b2GrowableStack
{
public:
	/**************************************************************************
	* 功能描述:栈构造函数,初始化相关数据
	* 参数说明:(void)
	* 返 回 值:(void)
	**************************************************************************/
	b2GrowableStack()
	{
		m_stack = m_array;
		m_count = 0;
		m_capacity = N;
	}
	/**************************************************************************
	* 功能描述:栈析构函数,释放相关内存
	* 参数说明:(void)
	* 返 回 值:(void)
	**************************************************************************/
	~b2GrowableStack()
	{
		if (m_stack != m_array)
		{
			b2Free(m_stack);
			m_stack = NULL;
		}
	}
	
	/**************************************************************************
	* 功能描述:进栈操作
	* 参数说明:element :进栈元素
	* 返 回 值:(void)
	**************************************************************************/
	void Push(const T& element)
	{
		//栈已满
		if (m_count == m_capacity)
		{
			//获取栈头指针并保存到old中
			T* old = m_stack;
			//将栈的容量扩充到原来的2倍
			m_capacity *= 2;
			//申请内存,并保存到m_stack中
			m_stack = (T*)b2Alloc(m_capacity * sizeof(T));
			//将原来的内容整体拷贝到m_stack中去
			std::memcpy(m_stack, old, m_count * sizeof(T));
			if (old != m_array)
			{
				//释放old指向的内存
				b2Free(old);
			}
		}
		//进栈,并将元素个数自加
		m_stack[m_count] = element;
		++m_count;
	}
	
	/**************************************************************************
	* 功能描述:出栈操作
	* 参数说明:(void)
	* 返 回 值:返回最近进入栈的值
	**************************************************************************/
	T Pop()
	{
		//验证元素个数的有效性
		b2Assert(m_count > 0);
		//元素个数自减,并出栈
		--m_count;
		return m_stack[m_count];
	}
	/**************************************************************************
	* 功能描述:获取栈中元素的个数
	* 参数说明:(void)
	* 返 回 值:栈中元素的个数
	**************************************************************************/
	int32 GetCount()
	{
		return m_count;
	}

private:
	//栈头指针
	T* m_stack;
	//辅助数组
	T m_array[N];
	//元素的个量
	int32 m_count;
	//栈的容量
	int32 m_capacity;
};

该类是个模板类,所有元素的类型和栈的大小均有使用的时候自己定义,同样是由内存池即辅助数组模拟的入栈和出栈。不同地方是当内存池不足时,b2GrowableStack会重新申请一个是原来一倍容量更大的新的内存池,并拷贝旧内存池中的内容到新的内存池中,同时释放除辅助数组以外的旧的内存池,不过多次扩充内存对将会有一定的影响,最好能够使用之前先估算一下所需内存池的大小。对于push/pop函数,均用数组模拟的进出栈。其他也没啥好说的了。









你可能感兴趣的:(C++,C++,引擎,box2D,box2D)