如何将 sf::Font 复制到自定义池分配的内存中?

How to copy sf::Font into custom pool allocated memory?

我为池分配器写了一个 class 并用一些基本结构测试了它,sf::Texture 它似乎工作正常。但是,当我使用它将 sf::Font 复制到分配的块中时,出现以下错误:

Exception thrown at 0x0F19009E (sfml-graphics-d-2.dll) in ChernoGame.exe: 0xC0000005: Access violation writing location 0xCDCDCDCD.

抛出此错误的代码:

ResourceType *newResource = (ResourceType*)allocator.alloc();
ResourceType t;
*newResource = t; //error is thrown from here

当我将它用于 sf::Texture 时它工作正常,仅当我使用 sf::Font

时才会抛出此错误

对于 sf::Font class 的大小是 76 字节,对齐是 4 字节,我的分配器遵循这个并为它分配一个 76 字节的块,对齐 4 字节,我可以'不知道如何解决这个错误。

编辑:我在 sf::SoundBuffer 上试过了,它抛出了类似的错误。

池分配器初始化:

bool PoolAllocator::init(const unsigned int & numBlocks, const unsigned int & blockSize, const int&alignment)
{
    if (mpMemoryBlock)
    {
        return false;
    }

//  assert(alignment & (alignment - 1) == 0);

    auto expandedBlockSize = alignUp(blockSize, alignment);

    mBlockSize = expandedBlockSize;
    mAlignment = alignment;
    mBlocks = numBlocks;

    mpMemoryBlock = malloc((expandedBlockSize * numBlocks) + alignment);

    if (!mpMemoryBlock)
    {
        return false;
    }

    auto currentBlock = alignUp((uintptr_t)mpMemoryBlock, alignment);
    nextFreeBlock = currentBlock;
    auto nextBlock = currentBlock;
    mpActualBlock = (void*)currentBlock;

    for (int i = 0; i < static_cast<int>(numBlocks); i++)
    {
        nextBlock = currentBlock + expandedBlockSize;
        auto alignedForNextPointerStorage = alignUp(currentBlock, sizeof(uintptr_t));
        *((uintptr_t*)alignedForNextPointerStorage) = nextBlock;
        currentBlock = nextBlock;
    }

    auto alignedForNextPointerStorage = alignUp(currentBlock, sizeof(uintptr_t));
    *((uintptr_t*)alignedForNextPointerStorage) = 0;

    return true;
}

池分配器分配:

void * PoolAllocator::alloc()
{
    if (*((uintptr_t*)nextFreeBlock) == 0)
    {
        return nullptr;
    }

    void *result = (void*)nextFreeBlock;
    nextFreeBlock = *((uintptr_t*)alignUp(nextFreeBlock, sizeof(uintptr_t)));
    return result;
}

池分配器解除分配:

void PoolAllocator::dealloc(void* address)
{
    auto nextPointer = alignUp((uintptr_t)address, sizeof(uintptr_t));
    if ((alignUp((uintptr_t)address, mAlignment) == (uintptr_t)address) && (mpActualBlock <= address) && !((uintptr_t)address >= ((uintptr_t)mpActualBlock + (mBlocks * mBlockSize))))
    {
        *(uintptr_t*)nextPointer = nextFreeBlock;
        nextFreeBlock = nextPointer;
    }
    else
    {
        throw std::runtime_error("Illegal deallocation of memory address : " + (uintptr_t)address);
    }

}

根据您的评论,您 "initializing" 通过简单地深度复制整个对象来创建这些新对象实例。

虽然这对于 C 结构可以很好地工作,但对于 non-trivial C++ classes 来说它会中断,因为它们将进行自己的内存分配。

想象一下下面两个简单的classes:

class A {
    int number = 5;
};

class B {
    int *number;
    B() : number(new int()) { *number = 5; }
    ~B() { delete number; }
}

现在假设您创建了一个 class A 的对象并复制它:

A a1;
A *a2 = reinterpret_cast<A*>(new char[sizeof(A)]);
memcpy(a2, &a1, sizeof(A));

你基本上会得到 class A 的两个对象。

现在让我们用 B 重复这个:

B b1;
B *b2 = reinterpret_cast<A*>(new char[sizeof(B)]);
memcpy(b2, &b1, sizeof(B));

这个好像也行?确实如此。但是,一旦其中一个对象被直接销毁(或超出范围),另一个对象也会破坏,从而导致访问冲突。为什么?让我们用范围扩展上面的例子:

B *b2 = reinterpret_cast<A*>(new char[sizeof(B)]);
{
    B b1;
    memcpy(b2, &b1, sizeof(B));
}

所以在这段代码 运行 之后,您假设 b2 指向 class B 的有效实例。它仍然有效——内存已分配——但 b2 中的指针不再有效。

  • 当您创建 b1 时,其构造函数将为一个整数分配 space 并存储该指针 (number)。
  • 此指针随后也被复制到 b2
  • 现在 b1 超出范围,析构函数释放分配的整数。
  • 现在 b2 仍然指向之前的整数,已经被 b1 释放了。
  • 如果您现在尝试访问整数,事情就会爆炸。

进一步说明,您正在做的事情——创建一个新对象并将其复制到分配给池的内存中——听起来是个好主意,但最终它破坏了池背后的全部目的allocators/memory管理:首先要避免重新分配对象。


值得庆幸的是,C++ 中内置了一个专门用于此目的的机制,称为 placement new。利用它,您应该能够执行以下操作:

#include <new>

// Other code here ...

sf::Font* myFont = new(fontAllocator.alloc()) sf::Font;

// Later to destroy the font:
myFont->~Font();
fontAllocator.dealloc(myFont);