本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.youkuaiyun.com/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函数,均用数组模拟的进出栈。其他也没啥好说的了。