2017-01-29 2 views
-3

Я написал класс для распределителя пула и протестировал его с некоторыми базовыми структурами, sf::Texture и, похоже, работает правильно. Однако, когда я использую его, чтобы скопировать sf::Font в выделенный блок я получаю следующее сообщение об ошибке:Как скопировать sf :: Шрифт в память выделенного пула?

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 размер класса составляет 76 байт, а выравнивание - 4 байта, мой распределитель выполняет это и выделяет ему блок из 76 байтов с выравниванием по 4 байт, Я не выясните, как решить эту ошибку.

Редактировать: Я пробовал его для sf::SoundBuffer и он бросает подобную ошибку.

бассейна инициализация Allocator:

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; 
} 

распределение Бассейна Allocator:

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

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

бассейна Allocator открепление:

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); 
    } 

} 
+0

Вы можете показать свой класс? –

+0

Как вы на самом деле инициализируете эти экземпляры? Похоже на то, что вы пропускаете конструктор SFML для 'sf :: Font', просто отбрасывая свою память. Многие конструкторы SFML нетривиальны и поэтому вы не можете просто пропустить их. – Mario

+0

@Mario Вот почему я создаю ResourceType t и копирую его в выделенном блоке, который должен скопировать построенный по умолчанию объект t в newResource правильно? –

ответ

1

По вашему комментарию, вы "инициализация" эти новые объектов путем простого глубокого копирования всех объектов.

Хотя это будет хорошо работать с C-структурами, оно имеет тенденцию разбиваться на нетривиальные классы C++, которые будут выполнять собственное назначение памяти.

Представьте себе следующие два простых классов:

class A { 
    int number = 5; 
}; 

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

Давайте теперь предположим, что вы создаете объект класса A и скопировать его:

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

Вы по существу в конечном итоге с двумя объектами класса 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 указывает на правильный экземпляр класса B. Он по-прежнему делает - выделяется память, но указатель внутри b2 больше не действителен.

  • При создании b1, его конструктор будет выделять пространство для целого и хранить этот указатель (number).
  • Этот указатель затем копируется в b2.
  • Теперь b1 выходит за пределы области действия, деструктор освобождает выделенное целое число.
  • Теперь b2 все еще указывает на прежнее целое число, уже освобожденное b1.
  • Если вы попытаетесь получить доступ к целому числу, все взорвется.

В качестве дополнительной записке, что вы делаете - создание нового объекта и копирование его в пул выделяемой памяти - звучит как хорошая идея, но в конце концов, побеждает всю цель позади бассейна распределители/управление памятью: вы хотите избежать перераспределения объектов в первую очередь.


К счастью, есть механика, встроенная в C++ именно для этой цели, которая называется размещения нового. Используя это, вы должны сделать что-то вроде следующего:

#include <new> 

// Other code here ... 

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

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

Большое спасибо, отличное объяснение, что касается альтернативы, которую вы предложили, у «размещения новых» есть аналогичные накладные расходы по сравнению с «новой» причиной, которая снова победит цель распределителя пулов, поскольку Я хочу избежать накладных расходов, вызванных «новым» –

+0

@KaranJoisher Нет накладных расходов при вызове * нового * или * размещения нового *. «Проблема» - фрагментация памяти **. Прочитайте эту тему, если хотите узнать больше. – Mario

+1

@KaranJoisher Короткая версия: 'new' должен сначала найти непрерывную память с правильным размером, что станет более сложным, чем фрагментировано адресное пространство для программы. Размещение нового (с распределителями пулов) позволяет пропустить это, что является приростом производительности/причиной. – Mario

Смежные вопросы