是否可以调整大小/重新分配大块内存而不会使指针无效?

时间:2014-11-10 02:22:05

标签: c++ memory

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

realloc能够做到这一点(但不能保证)吗?我正在寻找可以做到这一点的POSIX,Linux或Windows API,这样我就有了一席之地。

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

5 个答案:

答案 0 :(得分:1)

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

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

有两种简单的方法。首先,在std::vector中保留足够的内存。除了在高效系统上获取虚拟内存空间之外,这应该什么都不做。根据需要增长,并为这些虚拟内存页面分配物理页面。这在64位系统上更实用,因为你有一个巨大的内存空间。

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

答案 1 :(得分:1)

如果您特别希望保留原地的特定分配,malloc可能比最初需要的内存多得多。在现代操作系统上,它将保留虚拟地址空间 - 对于任何64位应用程序将实际上不受约束 - 并且只有在首次访问页面时才会寻求实际的物理后备内存,因此过度的初始分配极其“低成本”但可能会阻止realloc超出该大小的需要。对于你来说,这比摆弄或替换分配例程要容易得多....

如果你有很多分配和/或32位应用,你可能想要追求:

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

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

标准规定的malloc / free / realloc接口没有挂钩或选项,只有在就地重新分配时(并让它知道它何时“失败”),所以您需要编写自己的例程或编辑malloc - et-al库。

答案 2 :(得分:0)

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

您可能想要查看std::deque。它在许多方面就像一个std::vector(一个可增长的内存数组),但是当添加元素时,它不会为旧元素重新分配存储或在内存中移动现有对象。它确实有一些缺点 - 不能保证所有对象在内存中都是连续的,并且访问deque的元素稍微比访问vector元素慢,因为它有首先计算对象所在的块,然后计算块内元素的位置。通常情况下,除非非常性能至关重要,否则不会产生显着差异。

答案 3 :(得分:0)

好吧,因为你是用c ++写的:

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

    1. 当解除引用时不做任何额外的事情,只需解除引用
    2. 分配后,将变量添加到realloc()查找的列表中,以重新指定需要调整的任何指针。

这样做的缺点是,除非指向的对象类型非常少,否则让类型简单地工作可能会很繁琐。当然,这是模板的合适任务......

答案 4 :(得分:0)

以下是一些符合您要求的代码。如果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()内存块使用了新的大小。