0

So for learning purposes I'm trying to implement a custom memory-pool allocator. The implementation is currently very naive, but that's not the point.

When I try to allocate 8bytes (16bytes work), my program crashes at the third 8byte-allocation with the error Exception thrown: read access violation. this->**head** was 0xFFFFFFFFFFFFFFF7.

When I run the code outside my other project in isolation it works as expected. But I'm guessing that I might have some undefined behaviour which makes it work in isolation by plain luck. The version you see below is the code I use in isolation and the other project (where it crashes).

Any help you might be able to provide is very appreciated.

Gist of all code: https://gist.github.com/kALLEBALIK/8d9797399cf3bce8608834472d619f98

#include "FreeLinkedList.h"

class MemoryPool
{
    struct FreeHeader {};
    using  Node = FreeLinkedList<FreeHeader>::Node;
    FreeLinkedList<FreeHeader> free_list;

    size_t total_size;
    size_t used_size;
    size_t block_size;
    void* start_pointer;

    void increment();
    void decrement();

    void reset();

public:
    MemoryPool(const size_t total_size, const size_t block_size);
    ~MemoryPool();

    void* allocate(const size_t size, const size_t alignment = 0);
    void free(void* ptr);

    void print_memory_range(void* ptr, const size_t range) const;
    void print_memory() const;
};
// MemoryPool.cpp

#include "MemoryPool.h"
#include <iostream>
#include <iomanip>

void MemoryPool::increment() { this->used_size += this->block_size; }
void MemoryPool::decrement() { this->used_size -= this->block_size; }

MemoryPool::MemoryPool(const size_t total_size, const size_t block_size)
{
    this->total_size = total_size;
    this->block_size = block_size;
    this->start_pointer = malloc(this->total_size);

    reset();
}

MemoryPool::~MemoryPool()
{
    if (this->start_pointer != nullptr)
        free(this->start_pointer);
}

void MemoryPool::reset()
{
    const size_t block_count = this->total_size / this->block_size;
    for (unsigned int i = 0; i < block_count; ++i)
    {
        this->used_size = 0;
        const size_t mem_address = reinterpret_cast<size_t>(this->start_pointer) + i * block_size;
        free_list.release(reinterpret_cast<Node*>(mem_address));
    }
}

void* MemoryPool::allocate(const size_t size, const size_t alignment)
{
    Node* free_position = free_list.obtain();

    this->increment();

    return (void*)free_position;
}

void MemoryPool::free(void* ptr)
{
    this->decrement();

    free_list.release((Node*)ptr);
}

void MemoryPool::print_memory_range(void* ptr, const size_t range) const
{
    for (size_t k = 0; k < range; ++k)
    {
        printf("%02hhx ", *reinterpret_cast<unsigned char*>(reinterpret_cast<size_t>(ptr) + k));
    }
}

void MemoryPool::print_memory() const 
{
    for (size_t i = 0; i < total_size; i += block_size)
    {
        std::cout << "0x" << std::hex << std::noshowbase << std::setw(16) << std::setfill('0') << reinterpret_cast<size_t>(this->start_pointer) + i << ": ";
        print_memory_range(reinterpret_cast<void*>(reinterpret_cast<size_t>(this->start_pointer) + i), this->block_size);
        std::cout << std::endl;
    }
}
#pragma once

template <class T>
class FreeLinkedList
{
public:
    struct Node {
        T data;
        Node* next;
    };

    FreeLinkedList(FreeLinkedList&) = delete;
    FreeLinkedList()                = default;

    ~FreeLinkedList()
    {
        delete head;
    }

    void release(Node* new_node)
    {
        new_node->next = head;
        head = new_node;
    }

    Node* obtain()
    {
        if (head == nullptr)
            return nullptr;

        Node* last = head;
        head = head->next;
        return last;
    }
private:
    Node* head;
};

#include <iostream>
#include "MemoryPool.h"
#include <iomanip>

int main()
{

    struct b8
    {
        char a1 = 1;
        char a2 = 2;
        char a3 = 3;
        char a4 = 4;
        char a5 = 5;
        char a6 = 6;
        char a7 = 7;
        char a8 = 8;
    };

    struct b16
    {
        char a1 = 1;
        char a2 = 2;
        char a3 = 3;
        char a4 = 4;
        char a5 = 5;
        char a6 = 6;
        char a7 = 7;
        char a8 = 8;
        char a11 = 1;
        char a22 = 2;
        char a33 = 3;
        char a44 = 4;
        char a55 = 5;
        char a66 = 6;
        char a77 = 7;
        char a88 = 8;
    };

    std::cout << "b16 size: " << sizeof(b16) << std::endl; // 16
    std::cout << "b8  size: " << sizeof(b8)  << std::endl; //  8


    std::cout << "-------16-byte-------" << std::endl;
    MemoryPool* mp = new MemoryPool(128, 16);
    std::cout << "-before-" << std::endl;
    mp->print_memory();

    std::cout << "-first-" << std::endl;
    void* ptr11 = new(mp->allocate(16)) b16();
    mp->print_memory();

    std::cout << "-second-" << std::endl;
    void* ptr12 = new(mp->allocate(16)) b16();
    mp->print_memory();

    std::cout << "-third-" << std::endl;
    void* ptr13 = new(mp->allocate(16)) b16();
    mp->print_memory();

    std::cout << "-fourth-" << std::endl;
    void* ptr14 = new(mp->allocate(16)) b16();
    mp->print_memory();

    std::cout << "-fifth-" << std::endl;
    void* ptr15 = new(mp->allocate(16)) b16();
    mp->print_memory();

    std::cout << "-sixth-" << std::endl;
    void* ptr16 = new(mp->allocate(16)) b16();


    std::cout << "-------8-byte-------" << std::endl;
    MemoryPool* mp2 = new MemoryPool(64, 8);
    std::cout << "-before-" << std::endl;
    mp->print_memory();

    std::cout << "-first-" << std::endl;
    void* bptr11 = new(mp2->allocate(8)) b8();
    mp->print_memory();

    std::cout << "-second-" << std::endl;
    void* bptr12 = new(mp2->allocate(8)) b8();
    mp->print_memory();

    std::cout << "-third-" << std::endl;
    void* bptr13 = new(mp2->allocate(8)) b8(); // <----- Exception thrown: read access violation.this->**head** was 0xFFFFFFFFFFFFFFF7. occurred
    mp->print_memory(); 

    std::cout << "-fourth-" << std::endl;
    void* bptr14 = new(mp2->allocate(8)) b8();
    mp->print_memory();

    mp->free(ptr11);
    mp->free(ptr12);
    mp->free(ptr13);
    mp->free(ptr14);
    mp->free(ptr15);
    mp->free(ptr16);

    mp2->free(bptr11);
    mp2->free(bptr12);
    mp2->free(bptr13);
    mp2->free(bptr14);

    delete(mp);
    delete(mp2);
}

Print of visual studio error breakpoint: Print of visual studio breakpoint

Thanks in advance.

objectnabb
  • 93
  • 2
  • 9
  • Take care with your tragging. This ain't C. The extra eyes the irrelevant tag attracts probably have better things to do than answer questions in languages they aren't monitoring. – user4581301 Nov 28 '19 at 23:31
  • If you are permitted to do so, consider replacing your roll-your-own linked list with [`std::forward_list`](https://en.cppreference.com/w/cpp/container/forward_list) It's been used and abused by millions of programmers by now and should be bug free. – user4581301 Nov 28 '19 at 23:37
  • Note: You should also `delete` the assignment operator to eliminate all possibilities of accidental copying and a Rule of Three violation. – user4581301 Nov 28 '19 at 23:39
  • @user4581301 assignment operator of? :) – objectnabb Nov 28 '19 at 23:41
  • My apologies for the incomplete thought. I meant the assignment operator of `FreeLinkedList` – user4581301 Nov 28 '19 at 23:43
  • @user4581301 Like this? ```FreeLinkedList& operator=(const FreeLinkedList&) = delete;``` And what is Rule of Three violation? Thanks for your answer. – objectnabb Nov 28 '19 at 23:51
  • [The Rule of Three (and friends)](https://en.cppreference.com/w/cpp/language/rule_of_three). That's a bit of a dry read, so [here's an SO Question that details how it applies to you](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) – user4581301 Nov 28 '19 at 23:54
  • Thanks very much for the links, rly helpful. – objectnabb Nov 28 '19 at 23:57
  • Unrelated: MemoryPool.h should have a `#include ` to get access to `size_t`;. Otherwise it depends on a specific include ordering to compile. – user4581301 Nov 29 '19 at 00:02
  • Ah! I see. tyvm. – objectnabb Nov 29 '19 at 00:04

1 Answers1

0

head doesn't appear to be initialized to anything safe to use.

Node* head = nullptr;

ought to solve that.

user4581301
  • 33,082
  • 7
  • 33
  • 54
  • It did not unfortunately. – objectnabb Nov 28 '19 at 23:43
  • In that case I'm afraid to say you have at least two bugs. I'll see if I can spot the other one. You should add MemoryPool.h to the question to make it complete and then see if you can transform the the given code into a [mcve]. A complete and minimal example makes it easier for us to find the bug you're looking for (in case you have more than one) and making a MCVE often results in you finding and fixing the bug yourself without further help. – user4581301 Nov 28 '19 at 23:47
  • @objectnabb I'm not seeing how you place items into this linked list. – user4581301 Nov 28 '19 at 23:50
  • Wait. I see it. `release` is a bit oddly named. – user4581301 Nov 28 '19 at 23:52
  • Added MemoryPool.h, it should be reproducible now. – objectnabb Nov 28 '19 at 23:54
  • It strikes me more as a `put`, as in put item into list. Release usually means give back, which applies in this case when you are returning an item to the list; I just missed that you also used it to place an item into the list in the first place. – user4581301 Nov 28 '19 at 23:57
  • Would a copy of the memory help to diagnose? – objectnabb Nov 29 '19 at 00:08
  • Had to step through with a debugger. `FreeList` is getting trashed exiting `allocate`. I recommend placing a breakpoint and stepping though to see for yourself. – user4581301 Nov 29 '19 at 00:15
  • Hm i don't get it still. Stepped through and i think everything looks fine until the third allocation then it just craps the bed? – objectnabb Nov 29 '19 at 00:28
  • Oh I might see some problems but i don't understand what's going on. – objectnabb Nov 29 '19 at 00:38
  • 1
    I can't explain this one before I have go do paying work. You're not allocating enough storage for each `Node` Node is 16 bytes (on a 64 bit PC) but for the 8 byte block, you allocate 8 bytes. Each node bleeds into the next. – user4581301 Nov 29 '19 at 00:46
  • Oh shit... This was my first theory but i must have done sizeof(Node*) instead of sizeof(Node), I feel stupid. So is the only way to limit the allocation to 16bytes and up? Thanks for all your answers by the way, really appreciate it. – objectnabb Nov 29 '19 at 00:57
  • I think i fixed it, just changed ```struct Node { T data; Node* next; }; ``` to ```struct Node { Node* next; }; ``` And used the memory previosly used to store the pointer to store the data. – objectnabb Nov 29 '19 at 07:43
  • I don't think that will work for the 16 byte case. You'd need two `Node`s to fit the data you're going to store at the node. Consider using a `union` to make sure the size is always right. `union block {char data[BLOCKSIZE]; Node * next;};` Use with care because bigtime undefined behaviour potential. – user4581301 Nov 29 '19 at 17:50
  • It actually works with 16bytes, I'm just storing the data in the same memory as the pointer after the pointer is no longer needed. Here is some data dumps: before first alloc -> sixth alloc. https://gyazo.com/1d46637f4ca8a24f86d32654bd9eff25 – objectnabb Nov 29 '19 at 18:19