如何将 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);
我为池分配器写了一个 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);