繁体   English   中英

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

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

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

realloc是否可以做到这一点(但不能保证)? 我正在寻找可以做到这一点的POSIX,Linux或Windows API,因此我有一个起点。

另外,我还要感谢与创建内存管理系统有关的任何其他阅读材料。

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

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

有两种简单的方法。 首先,在std::vector保留足够的内存。 除了在高效的系统上获取虚拟内存空间外,此操作无济于事。 根据需要进行扩展,这些虚拟内存页面将被分配物理页面。 这在64位系统上更为实用,因为您拥有巨大的内存空间。

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

如果有,你是特别热衷于保持就地特定的分配,它可能是有意义的malloc比最初需要更多的内存。 在现代的OS上,它将保留虚拟地址空间-对于任何64位应用程序都将实际上不受限制-并且仅在首次访问页面时才寻求实际的物理后备内存,因此过多的初始分配是极其“低成本”的但可能会阻止必要realloc该尺寸超出。 对您来说,这比摆弄或替换分配例程要容易得多。

如果您有很多分配和/或32位应用程序,则可能需要追求以下能力:

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

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

标准规定的malloc / free / realloc接口没有钩子或选项,仅当它们就位时才进行重新分配(并在发生“故障”时通知您),因此您将需要编写自己的例程或编辑malloc -et-图书馆。

如果不编写自己的分配器或采用第三方分配器,就无法增长malloc的块并保证它不会移动。 Realloc可能会移动它,并且如果它无法在原地增长,则无法强迫它失败。

您可能需要研究std::deque 它在很多方面都像std::vector (可增长的内存数组),但是添加元素时,它永远不会为旧元素重新分配存储或移动内存中的现有对象。 它确实有一些缺点-有不能保证所有的目的将在存储器中连续,并访问所述双端队列的元素比访问一个向量元素稍微慢一些,因为它必须首先计算阻断的对象是在,然后计算元素在块中的位置。 通常,除非性能至关重要,否则这不会产生明显的变化。

好吧,因为您是用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 = %p\n",
        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 %p\n",
            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()内存块都使用了新大小。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM