是否可以在不使指针无效的情况下调整/重新分配大块内存

Is it possible to resize/reallocate a large chunk of memory without invalidating pointers?

本文关键字:新分配 分配 内存 调整 指针 情况下 无效 是否      更新时间:2023-10-16

我正在为我的游戏引擎开发一个分配器系统,我想知道是否可以在不使指向内存中位置的指针无效的情况下重新分配内存(PageSize的倍数)。这可以使用虚拟内存接口实现吗?我知道虚拟内存分页不适用于DMA/固定内存,而且在控制台上也不可用。

realloc能做到这一点吗(但不能保证)?我正在寻找一个可以做到这一点的POSIX、Linux或Windows api,这样我就有了一个起点。

此外,我将感谢任何与创建内存管理系统相关的进一步阅读,谢谢。

地址空间的虚拟性与您提出的解决方案并不完全相关。不可能使用更大的块的原因是,地址空间中存在您希望阵列增长到的数据。这在物理内存空间和虚拟内存空间上是相同的问题。

我可以看到两种替代方法。

有两种简单的方法。首先,在std::vector中保留足够的内存。这只会在高效的系统上获得虚拟内存空间。根据需要扩展它,这些虚拟内存页面将被分配物理页面。这在64位系统上更实用,因为您有巨大的内存空间。

另一种方法是创建自己的类似分段的数组。有一个向量,它具有指向页面大小块的唯一指针,以及一个隐藏该细节的包装器。这为迭代器访问添加了另一层间接层。为琐碎的可复制类型和其他可选操作实现快速blit。

如果有一个特定的分配是你特别想保留的,那么malloc可能比最初需要的内存多得多。在现代操作系统上,这将保留虚拟地址空间——这对任何64位应用程序来说都是不受限制的——并且只有在首次访问页面时才会寻找实际的物理后备内存,因此过多的初始分配非常"低成本",但可能会阻止对超过该大小的realloc的需求。这对你来说比篡改或替换分配例程要容易得多。。。。

如果你有很多分配,和/或一个32位的应用程序,你可能想追求以下能力:

  • 询问realloc是否可以操作到位(例如,添加一个额外的函数自变量以防止移动到另一个地址,并在失败时返回nullptr);这种方法减少了指针调整,而不一定在分配虚拟地址空间时更积极,但有时仍需要处理移动的问题;和/或

  • 拦截mallocrealloc调用,并为它们分配比请求的虚拟地址空间多得多的虚拟地址,以减少(或在给定应用程序特定知识的情况下消除)在稍后的realloc期间需要移动内容的风险。

标准强制的malloc/free/realloc接口没有挂钩或选项,只有在适当的情况下才能重新分配(并在"失败"时通知您),因此您需要编写自己的例程或编辑malloc-等库。

如果不编写自己的分配器或采用第三方分配器,就无法增长malloc’ed块并保证它不会移动。Realloc可能会移动它,如果它不能生长到位,就没有办法强迫它失败。

您可能需要查看std::deque。它在很多方面都像std::vector(一个可增长的内存阵列),但当添加元素时,它永远不会为旧元素重新分配存储空间,也不会移动内存中的现有对象。它确实有一些缺点——不能保证所有对象在内存中都是连续的,访问deque的元素比访问向量元素慢一点,因为它必须首先计算对象在哪个块中,然后计算元素在块中的位置。通常情况下,这不会产生明显的差异,除非性能极为关键。

好吧,既然你是用c++写的:

  • 使用专用版本重载malloc()/realloc()/free()
  • 不要使用裸指针,而是使用一个特殊的类,比如class myptr哪个

    1. 当取消引用时不会做任何额外的事情,只是取消引用
    2. 赋值后,将变量添加到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()内存块都使用了新的大小。