是否可以在不使指针无效的情况下调整/重新分配大块内存
Is it possible to resize/reallocate a large chunk of memory without invalidating pointers?
我正在为我的游戏引擎开发一个分配器系统,我想知道是否可以在不使指向内存中位置的指针无效的情况下重新分配内存(PageSize的倍数)。这可以使用虚拟内存接口实现吗?我知道虚拟内存分页不适用于DMA/固定内存,而且在控制台上也不可用。
realloc能做到这一点吗(但不能保证)?我正在寻找一个可以做到这一点的POSIX、Linux或Windows api,这样我就有了一个起点。
此外,我将感谢任何与创建内存管理系统相关的进一步阅读,谢谢。
地址空间的虚拟性与您提出的解决方案并不完全相关。不可能使用更大的块的原因是,地址空间中存在您希望阵列增长到的数据。这在物理内存空间和虚拟内存空间上是相同的问题。
我可以看到两种替代方法。
有两种简单的方法。首先,在std::vector
中保留足够的内存。这只会在高效的系统上获得虚拟内存空间。根据需要扩展它,这些虚拟内存页面将被分配物理页面。这在64位系统上更实用,因为您有巨大的内存空间。
另一种方法是创建自己的类似分段的数组。有一个向量,它具有指向页面大小块的唯一指针,以及一个隐藏该细节的包装器。这为迭代器访问添加了另一层间接层。为琐碎的可复制类型和其他可选操作实现快速blit。
如果有一个特定的分配是你特别想保留的,那么malloc
可能比最初需要的内存多得多。在现代操作系统上,这将保留虚拟地址空间——这对任何64位应用程序来说都是不受限制的——并且只有在首次访问页面时才会寻找实际的物理后备内存,因此过多的初始分配非常"低成本",但可能会阻止对超过该大小的realloc
的需求。这对你来说比篡改或替换分配例程要容易得多。。。。
如果你有很多分配,和/或一个32位的应用程序,你可能想追求以下能力:
-
询问
realloc
是否可以操作到位(例如,添加一个额外的函数自变量以防止移动到另一个地址,并在失败时返回nullptr
);这种方法减少了指针调整,而不一定在分配虚拟地址空间时更积极,但有时仍需要处理移动的问题;和/或 -
拦截
malloc
和realloc
调用,并为它们分配比请求的虚拟地址空间多得多的虚拟地址,以减少(或在给定应用程序特定知识的情况下消除)在稍后的realloc
期间需要移动内容的风险。
标准强制的malloc
/free
/realloc
接口没有挂钩或选项,只有在适当的情况下才能重新分配(并在"失败"时通知您),因此您需要编写自己的例程或编辑malloc
-等库。
如果不编写自己的分配器或采用第三方分配器,就无法增长malloc’ed块并保证它不会移动。Realloc可能会移动它,如果它不能生长到位,就没有办法强迫它失败。
您可能需要查看std::deque
。它在很多方面都像std::vector
(一个可增长的内存阵列),但当添加元素时,它永远不会为旧元素重新分配存储空间,也不会移动内存中的现有对象。它确实有一些缺点——不能保证所有对象在内存中都是连续的,访问deque的元素比访问向量元素慢一点,因为它必须首先计算对象在哪个块中,然后计算元素在块中的位置。通常情况下,这不会产生明显的差异,除非性能极为关键。
好吧,既然你是用c++写的:
- 使用专用版本重载malloc()/realloc()/free()
-
不要使用裸指针,而是使用一个特殊的类,比如
class myptr
哪个- 当取消引用时不会做任何额外的事情,只是取消引用
- 赋值后,将变量添加到realloc()查看的列表中,以重新指向任何需要调整的指针
这样做的缺点是,除非指向的对象类型很少,否则让这些类型简单地工作可能会很乏味。当然,这是一个适合模板的任务。。。
这里有一些代码可以满足您的要求。如果realloc()移动内存块,那么它会找到并修复块中的指针。这是受到BDWGC项目的启发,但他们不应受到指责。这个代码的危险在于它会在内存中找到一个看起来像指针但不是指针的单词。
struct dbbExprAllocFixup {
typedef dbbExpr * dbbExprPtr;
dbbExprAllocFixup( void *, size_t, void * );
void CheckExpr( dbbExprPtr & );
void CheckOne( void *, char * tag );
void CheckRange();
char * oldBase; // old realloc() ptr
char * oldEnd;
char * newBase; // new realloc() ptr
char * newEnd;
ptrdiff_t delta;
};
dbbExprAllocFixup::dbbExprAllocFixup( void * ob, size_t ns, void * nb )
{
oldBase = (char *) ob;
oldEnd = oldBase + ns;
newBase = (char *) nb;
newEnd = newBase + ns;
delta = newBase - oldBase;
dbbTrace::Output( "Expr realloc: (%p to %p) moved to %p, delta = %pn",
oldBase, oldEnd, newBase, delta );
}
void
dbbExprAllocFixup::CheckOne( void * p, char * tag )
{
char * * scan = (char * *) p;
char * value = * scan;
if( value >= oldBase && value <= oldEnd ) {
// This value needs fixing
* scan = * scan + delta;
dbbTrace::Output( " Expr realloc: %s old value %p new value %pn",
tag, value, * scan );
}
}
void
dbbExprAllocFixup::CheckExpr( dbbExprPtr & p )
{
if( p != 0 ) {
CheckOne( & p, "e" );
}
}
void
dbbExprAllocFixup::CheckRange()
{
char * * scan;
for( scan = (char * *) newBase; scan < (char * *) newEnd; scan++ ) {
CheckOne( scan, "r" );
} // for
}
如果需要,调用realloc()和dbbExprAllocFixup的代码。
void * p = ::realloc( m_pHead, bytes );
if( p == m_pHead ) {
// The memory did not move, do nothing
} else {
// The memory moved
dbbExprAllocFixup f( m_pHead, bytes, p );
// Fix pointer in the block to locations in the block
f.CheckRange();
// Fix pointers outside the block that point into the block
f.CheckExpr( pExpr->m_pRoot );
for( int i = 0; i < pExpr->m_MLRootSize; i++ ) {
f.CheckExpr( pExpr->m_pMLRoot[i] );
}
m_pHead = (dbbExprAllocChunk *) p;
} // if
此代码未在使用中,因此很容易出现错误。我现在注意到的一个错误是,它对旧的和新的realloc()内存块都使用了新的大小。
- C++ 如何在将新对象分配给另一个对象时创建新对象
- 如何增加以前由新运算符分配的 C++ std::list 数组的大小?
- 为模板参数类型中的新对象分配内存
- 新运算符分配的大小大于声明的大小.为什么
- 在C++中分配分配
- 为什么支撑初始化分配分配填充垃圾变量
- C 新操作员分配新内存
- 新不分配内存?
- 如果我的C++“新”内存分配失败,如何找出返回值
- 我可以用新的分配内存块吗?
- COM / DCOM:服务器存根不会为现有接口中的新方法分配内存
- 在线程在 C++ 中完成后将新任务分配给线程
- 只能使用CUDA中的新运算符分配有限的内存
- 与将新内容分配给向量的指针斗争
- 确定C++中新运算符分配的内存大小
- 按新内存分配
- 是否有必要使用 'new' 将新指针分配给与指向同一结构的先前存在的指针相同的结构?如果是这样,为什么?
- 用于新运算符分配的公共内存
- boost::io_service post方法是否引起新的分配?
- 如何在C++中检索由新运算符分配的对象的地址