C++ Debugging Help Memory Leak - c++

I am currently working on an assignment and I am struggling greatly with debugging my memory leak. My program runs and passes however, when I upload it for grading my code has a memory leak. If someone could point me in the right direction on how to fix it I would greatly appreciate!
I've tried everything I could think of!
#ifndef A2_HPP
#define A2_HPP
#include <algorithm>
class sorted_sc_array {
public:
/*
* return: none
* constructor with no argument assign size_ = 0 and ptr_ to null pointer
*/
sorted_sc_array() : size_(0), ptr_(nullptr) {
}
/*
* return: none
* destructor delete the pointer ptr_
*/
~sorted_sc_array() {
delete[] ptr_;
}
/*
* return: none
* when assign an object to new object
*/
sorted_sc_array(const sorted_sc_array& A){
const signed char* str = A.data();
int sz = A.size_;
this->size_ = 0;
for(int i = 0; i < sz; i++) this->insert(str[i]);
delete[] str;
}
/*
* return: sorted_sc_array
* overloading of operator =
*/
sorted_sc_array& operator=(const sorted_sc_array& A){
const signed char* str = A.data();
int sz = A.size_;
this->size_ = 0;
for(int i = 0; i < sz; i++) this->insert(str[i]);
}
/*
* return int
* return the size of the ptr_
*/
int size() const {
return size_;
}
/*
* return char*
* return the deta stored in the pointer ptr_
*/
const signed char* data() const {
return ptr_;
}
/*
* return void
* add new char to the pointer ptr_ and sort the the new string after the addition
*/
void insert(signed char c) {
signed char *str = (signed char*)malloc((size_ + 1)*sizeof(char));
for(int i = 0; i < size_; i++) str[i] = ptr_[i];
str[size_++] = c;
ptr_ = (signed char*)malloc((size_)*sizeof(char));
for(int i = 0; i < size_; i++) ptr_[i] = str[i];
std::sort(ptr_, ptr_ + size_);
delete[] str;
}
private:
int size_; // size of the array
signed char* ptr_; // pointer to the array
}; // class sorted_sc_array
#endif // A2_HPP
This is the testing class:
/*
* File: a2.pp
* Description: testing class a2.hpp
*/
#include <iostream>
#include "a2.hpp"
int main(int argc, char* argv[]) {
sorted_sc_array A;
{
sorted_sc_array T;
for (signed char c = -128; c < 127; ++c) T.insert(c);
T = T;
sorted_sc_array V = T;
A = V;
}
const auto first = A.data();
const auto last = first + A.size();
auto size = A.size();
bool res = std::is_sorted(first, last);
if (!res || (A.size() != 255)) std::cout << "fail";
else std::cout << "pass";
std::cout << std::endl;
return 0;
} // main
The code compiles and executes, with a "pass" however there is a memory leak somewhere ! :(

There are multiple bugs in the shown code.
In the copy constructor:
delete[] str;
This deletes the other object's buffer. The other object's destructor will attempt to delete[] its own buffer again, when it gets destroyed. This will result in memory corruption, and undefined behavior.
The obvious memory leak is in your insert():
ptr_ = (signed char*)malloc((size_)*sizeof(char));
There are two simultaneous bugs here.
Using malloc for a buffer that the destructor will eventually delete[]. Only new-ed objects can be delete[]d. Using delete for a malloc-ed content is undefined behavior.
The previous contents of ptr_ are not explicitly deleted, thus leaking memory.
The insert(), overall, is doing unnecessary allocation. There's no need to allocate a buffer twice, there. Only one allocation is sufficient: allocate, copy over the content to the new buffer, delete the old ptr_, and set ptr_ to the newly-allocated buffer.

Related

How can I create a destructor to check if the pointer is NULL? If not, delete the memory that has been allocated for the pointer

CheckedArray::CheckedArray(int size) :mSize(size){
int *mArray = new int[size];
for(int i = 0; i < size; i++)
mArray[i] = 0;
}
CheckedArray::~CheckedArray() {
if (mArray == NULL){
return;
}
else {
delete[] mArray;
}
}
I'm using dynamic memory allocation to create a new array. I want to check if the pointer is null, then return. If not, then delete. I'm getting these error messages, but I have no idea what's wrong.
(9094,0x100094600) malloc: *** error for object 0x10001e7b3: pointer being freed was not allocated
(9094,0x100094600) malloc: *** set a breakpoint in malloc_error_break to debug
To be completely clear
CheckedArray::CheckedArray(int size) :mSize(size){
int *mArray = new int[size];
for(int i = 0; i < size; i++)
mArray[i] = 0;
}
should be
CheckedArray::CheckedArray(int size) :mSize(size), mArray(new int[size]){
for(int i = 0; i < size; i++)
mArray[i] = 0;
}
Your version creates a local variable mArray which shadows the class variable of the same name.
Here is an example of what std::unique_ptr can do for you :
#include <iostream>
#include <algorithm>
#include <memory>
// https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#r11-avoid-calling-new-and-delete-explicitly
// so use https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique
// prefer range based for loops, they can't go out of bounds : https://en.cppreference.com/w/cpp/language/range-for
class dynamic_int_array_t final
{
public:
// creates an "empty array" with enough memory for 4 ints.
dynamic_int_array_t() :
m_capacity{ 4ul }, // start with a capacity for 4 ints.
m_size{ 0ul }, // but actually none are stored yet
m_values{ std::make_unique<int[]>(m_capacity) }
{
}
// allows you to construct an array from a list of integers
dynamic_int_array_t(std::initializer_list<int>&& values) :
m_capacity{ values.size() },
m_size{ values.size() },
m_values{ std::make_unique<int[]>(m_capacity) }
{
std::copy(values.begin(), values.end(), m_values.get());
}
~dynamic_int_array_t() = default; // destructor will destruct unique_ptr and free memory
// non-copyable non-movable (simplifies things for now)
dynamic_int_array_t(const dynamic_int_array_t&) = delete;
dynamic_int_array_t& operator=(const dynamic_int_array_t&) = delete;
dynamic_int_array_t(dynamic_int_array_t&&) = delete;
dynamic_int_array_t& operator=(dynamic_int_array_t&&) = delete;
// begin and end allow range based for loops to work
// range based for loops don't allow you to go out of bounds.
auto begin() const
{
return m_values.get();
}
// end should point "one past" the array (that's how end works)
auto end() const
{
int* ptr = begin();
ptr += m_size;
return ptr;
}
std::size_t size() const
{
return m_size;
}
void add(const int value)
{
// if not enough memory then allocate more
if (m_size == m_capacity) grow_capacity();
// add new value at the end
m_values[m_size] = value;
m_size++;
}
// add another array to this one
void append(const dynamic_int_array_t& rhs)
{
for (int value : rhs)
{
add(value);
}
}
private:
void grow_capacity()
{
m_capacity *= 2;
// allocate new memory
auto tmp = std::make_unique<int[]>(m_capacity);
// copy content to new memory
std::copy(begin(), end(), tmp.get());
// swap new memory with tmp so m_values will now be the newly allocated memory and tmp will hold the previously allocated memory
std::swap(tmp, m_values);
// tmp will go out of scope and delete old buffer
}
std::size_t m_capacity;
std::size_t m_size;
std::unique_ptr<int[]> m_values;
};
int main()
{
dynamic_int_array_t array{ 4,5 };
for (int n = 10; n < 20; ++n)
{
array.add(n);
}
for (const int value : array)
{
std::cout << value << " ";
}
return 0;
}

GCC "AddressSanitizer: heap-buffer-overflow" when initializing struct

I've been writing an VM/Interpreter combination thingy, I don't know how to exactly describe it.
Everything behaved as it should, now before I have hundreds of lines of code, I wanted to go into Garba Collection, because there were some pointers which somehow got lost, in some way. Not that I didn't delete pointers, I created, but they somehow got lost in the proccess of interpreting/running the code.
So, I wanted to track them. I wrote my own "Memory Manager" in some way, it's just a std::vector, where I collect all pointers in.
To track and allocate pointers, I have following code:
struct MemBlock {
bool free;
void* ptr;
size_t size;
};
std::vector<MemBlock*> mem;
size_t max_size;
size_t mem_size;
int count = 0;
void mem_init(size_t maxSize) {
max_size = size/sizeof(MemBlock*);
}
void* mem_alloc(size_t size) {
for (int i = 0; i < count; i++) {
MemBlock* block = mem[i];
if (block->free) {
mem_size -= block->size;
mem_size += size;
block->free = false;
block->ptr = malloc(size);
block->size = size;
if (block->ptr == nullptr) {
throw std::exception();
}
return block->ptr;
}
}
void* ptr = malloc(sizeof(size));
if (ptr == nullptr) {
throw PointerNullException();
}
MemBlock* block = (MemBlock*) malloc(sizeof(MemBlock));
*block = (MemBlock) {
false,
ptr,
size
};
mem_size += size;
count++;
mem.push_back(block);
return block->ptr;
}
But as soon, as I use mem_alloc() and initialize the object inside of the pointer:
Int* i = (Int*) mem_alloc(sizeof(Int));
*i = (Int) {}; // -- Here
i->value = atoi(advance().c_str());
The GCC AdressSanitizer shows following error:
==5939==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000098 at pc 0x555963d82fc5 bp 0x7fff4ec39070 sp 0x7fff4ec39060
WRITE of size 4 at 0x602000000098 thread T0
If I remove said line, then it just occurs on the nex line. The the pointer does point to a valid memory location, if not it should've had thrown an exception.
I'm sure that I missed something/did something wrong, of course.
But I don't know what. This is how I learned it, or at least what I understood...
Edit:
This would be a minimal reproducible Example:
#include <iostream>
#include <stdlib.h>
#include <vector>
struct Object {
const char* type;
};
template <typename T>
struct Primitive : Object {
T value;
};
struct Int : Primitive<int> {
const char* type = "int";
};
struct MemBlock {
bool free;
void* ptr;
size_t size;
};
std::vector<MemBlock*> mem;
size_t mem_size = 0;
int count = 0;
void* mem_alloc(size_t size) {
for (int i = 0; i < count; i++) {
MemBlock* block = mem[i];
if (block->free) {
mem_size -= block->size;
mem_size += size;
block->free = false;
block->ptr = malloc(size);
block->size = size;
if (block->ptr == nullptr) {
throw std::exception();
}
return block->ptr;
}
}
void* ptr = malloc(sizeof(size));
MemBlock* block = (MemBlock*) malloc(sizeof(MemBlock));
*block = (MemBlock) {
false,
ptr,
size
};
mem_size += size;
count++;
mem.push_back(block);
std::cout << "HI" << std::endl;
return block->ptr;
}
void mem_free(void* ptr) {
for (int i = 0; i < count; i++) {
MemBlock* block = mem[i];
if (block->ptr == ptr) {
free(ptr);
mem_size -= block->size;
block->size = 0;
block->ptr = nullptr;
block->free = true;
}
}
}
int main() {
// Create new Integer-Object
Int* i = (Int*) mem_alloc(sizeof(Int));
std::cout << "[Pointer]: " << i << std::endl;
*i = (Int) {};
i->value = 5;
std::cout << "[Value]: " << i->value << std::endl;
}
Well, thanks to Retired Ninja and Richar Critten, I've got the solution.
In mem_alloc() I've used sizeof(size) to allocate memory to the pointer, which of course is wrong. I guess my head was pretty much off after hours of coding.
But I guess this problem is now solved.

Why do I keep getting a segmentation fault with my delete function

I have an assignment where I need to use an overloaded destructor to delete dynamically allocated pointers. However, when it runs, some of my pointers are deleted until a segmentation fault with one of my objects' pointers the one pointing to " and the second" made with a parameterized constructor.
I have tried to go through and make sure the delete operator had brackets (because my new operator did) I made sure the object still existed by printing out its information and address. I have tried to rewrite my allocation function, and I have tried to go over my destructor to see where it messes up.
If it helps, I have included my destructor, my allocation function, my deallocation function, and my parameterized constructor.
'''
//Destructor
MyString::~MyString()
{
buffer_deallocate();
};
void MyString::buffer_deallocate() {
cout << m_buffer << endl;
delete[](m_buffer);
m_buffer = NULL;
m_size = 0;
}
void MyString::buffer_allocate(size_t size) {
try {
m_buffer = new char[size];
m_size = size;
}
catch(bad_alloc&)
{
cout << "Errror: Unable to allocate memory" << endl;
buffer_deallocate();
}
}
//Parameterized Constructor
MyString::MyString(const char * str)
:m_size(0)
{
const char * strPtr = str;
while(*strPtr)
{
strPtr++;
m_size++;
}
buffer_allocate(m_size);
for(int i = 0; i < m_size; i++)
{
m_buffer[i] = str[i];
}
};
'''
Every time however I get the output after "and the second" to be
Segmentation fault (core dumped)
Edit: I have tried the majority of what was recommended. At least what I understood, the problem still persists, and I realize now I was kind of sparse on my code. (Please forgive me, I'm still learning.) Here is the new code along with the rest of the function file for reference:
'''
#include<iostream>
#include<string.h>
using namespace std;
#include"MyString.h"
//Default Constructor
MyString::MyString()
:m_size(0), m_buffer(NULL)
{
buffer_allocate(0);
};
//Parameterized Constructor
MyString::MyString(const char * str)
:m_size(strlen(str)+1), m_buffer(NULL)
{
buffer_allocate(m_size);
strncpy(m_buffer, str, m_size);
};
//Copy Constructor
MyString::MyString(const MyString & other)
:m_size(0), m_buffer(NULL)
{
const char * otherPtr = other.c_str();
buffer_allocate(other.size());
for(int i = 0; i < size(); i++)
{
m_buffer[i] = otherPtr[i];
}
m_buffer[m_size] = '\0';
};
//Destructor
MyString::~MyString()
{
buffer_deallocate();
};
size_t MyString::size() const
{
return m_size;
}
size_t MyString::length() const{
return m_size-1;
}
const char * MyString::c_str() const{
return m_buffer;
}
bool MyString::operator==(const MyString & other) const {
char * m_bufferPointer = m_buffer;
while(*m_bufferPointer++)
{
const char * str_ptr = other.c_str();
if(*m_buffer != *str_ptr++)
{
return 0;
}
}
return 1;
}
MyString & MyString::operator=(const MyString & rhs) {
buffer_deallocate();
buffer_allocate(rhs.size());
const char * c_strPtr = rhs.c_str();
int i;
for(i = 0; i < rhs.size(); i++)
{
this->m_buffer[i] = c_strPtr[i];
}
return *this;
}
MyString MyString::operator+ (const MyString & other_myStr) const {
char * temp_pointer;
temp_pointer;
size_t temp_size = m_size + other_myStr.size();
//New Combined Buffer for Concatanation
try {
temp_pointer = new char[temp_size];
temp_pointer = strcat(this->m_buffer, other_myStr.c_str());
}
catch(bad_alloc&)
{
cout << "Error: Unable to Allocate Memory";
return NULL;
}
return MyString(temp_pointer);
}
char & MyString:: operator[](size_t index) {
return m_buffer[index];
}
const char & MyString::operator[] (size_t index) const {
return m_buffer[index];
}
ostream & operator << (ostream& os, const MyString & myStr) {
os << myStr.m_buffer;
return os;
}
void MyString::buffer_deallocate() {
cout << "Trying to delete : " <<m_buffer << endl;
if(m_buffer){
delete[](m_buffer);
}
cout << " Success" <<endl;
m_buffer = NULL;
m_size = 0;
}
void MyString::buffer_allocate(size_t size) {
try {
m_buffer = new char[size];
m_size = size;
}
catch(bad_alloc&)
{
cout << "Errror: Unable to allocate memory" << endl;
m_size = 0;
}
}
'''
in MyString::buffer_deallocate
cout << m_buffer << endl;
requires that m_buffer be null-terminated. Unfortunately, MyString::MyString(const char * str) does not make this guarantee.
You could
for(int i = 0; i < m_size; i++)
{
cout << m_buffer[i] << endl;
}
to print the string out character by character instead, but it is probably more useful to waste a byte, null terminate, and take advantage of the Standard library
MyString::MyString(const char * str)
:m_size(0)
{
const char * strPtr = str;
while(*strPtr)
{
strPtr++;
m_size++;
}
buffer_allocate(m_size);
for(int i = 0; i < m_size; i++)
{
m_buffer[i] = str[i];
}
m_buffer[m_size] = '\0'; // add the null
}
and then
void MyString::buffer_allocate(size_t size) {
try {
m_buffer = new char[size+1]; // +1 for the null terminator
m_size = size;
}
catch(bad_alloc&) // this is a bad thing to do here. More on that later.
{
cout << "Errror: Unable to allocate memory" << endl;
buffer_deallocate();
}
}
But we can simplify this with a few library function calls.
MyString::MyString(const char * str)
:m_size(strlen(str))
{
buffer_allocate(m_size);
strcpy(m_buffer, str);
}
Addendum:
Your class may be violating the Rule of Three. If MyString does not have a copy constructor and an assignment operator to go along with the destructor any copies, deliberate or accidental, will turn a MyString into a time bomb. The destructor of one of the copies will be run before the others leaving the others without a valid m_buffer.
MyString::buffer_allocate cannot safely return void unless it allows the exception to propagate. Catching the bad_alloc leaves the object without a valid allocation at m_buffer and the rest of the program will not know this. Either every other access in the program must test for a valid buffer or engage in undefined behaviour trying to access the invalid memory. It's likely better to let the exception fall through and be caught by another part of the program that is better suited to making a decision on what to do.
MyString::buffer_allocate will leak an existing allocation if called on a MyString that already has a valid allocation at m_buffer. I recommend a
if (m_buffer)
{
delete[] m_buffer;
}
and initializing m_buffer to null in MyString's constructor
MyString(const char* str)
: m_size(std::strlen(str)), m_buffer(nullptr)
{
buffer_allocate(m_size);
std::strncpy(m_buffer, str, m_size);
}
So I had to remake this to some working code.
The problem might be that a string length must be extended with 1 to add the nul-termination. So that makes:
// prevent some MSVS warnings->errors
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <cstring>
//Destructor
class MyString {
private:
size_t m_size;
char* m_buffer;
void buffer_allocate(size_t size) {
try {
m_buffer = new char[size];
m_size = size;
}
catch (std::bad_alloc&)
{
std::cout << "Errror: Unable to allocate memory\n";
// allocation failed, nothing changed, don't delete/
m_size = 0;
}
}
void buffer_deallocate() {
// printing in the buffer deallocation??
delete[] m_buffer;
m_buffer = nullptr;
m_size = 0;
}
public:
MyString(const char* str)
: m_size(std::strlen(str)+1) // add null termination
, m_buffer(nullptr)
{
buffer_allocate(m_size);
std::strncpy(m_buffer, str, m_size);
}
~MyString() {
buffer_deallocate();
}
void Print() const {
std::cout << m_buffer << '\n';
}
};
int main() {
std::string input{ "Hello World!" };
MyString myString(input.c_str());
myString.Print();
}

initializing char* a = new char[size] does not work

I have a problem about initializing char* a = new char[size]. Here is my code.
class Practice
{
public:
Practice(const char* a);
~Practice();
const char* getString() const;
private:
char* mString;
int mSize;
};
#include "Practice.h"
Practice::Practice(const char * a)
:mSize(0)
,mString(nullptr)
{
while (a[mSize] != '\0')
{
mSize++;
}
mString = new char[mSize];
for (int i = 0; i < mSize; i++)
{
mString[i] = a[i];
}
}
Practice::~Practice()
{
delete[] mString;
}
const char* Practice::getString() const
{
return mString;
}
int main()
{
Practice p("Hello");
std::cout << p.getString() << std::endl;
return 0;
}
I expected the result is Hello.
But the result was like Hello²²²²▌▌▌▌▌▌▌■a%{▌.
I thought I initialized the mString member variable through mString = new char[mSize]. But it was not working the way I thought.
Can anybody enlighten me what's wrong with my code and fix it?
mString is not NUL-terminated.
Your're checking a constructor parameter for \0. But you are not allocating mString to put the same \0 there, and don't copy \0 from a.
So, resulting mString is not properly NULL-terminated and it would be read beyond end.
Reading beyond end of allocated is undefined behavior. In particular case, it is likely that either mString would output until some accidental zero, or crash would occur.
while (a[mSize] != '\0')
{
mSize++;
}
This sets mSize equal to the length of a, excluding the terminating '\0'. You should include the terminator in your copy:
while (a[mSize++])
{
}
or simply:
mSize = strlen(a) + 1;
You are not null-terminating your mString data, but it is expecting to be null-terminated when you pass it to std::cout. Without that terminator, std::cout reads into surrounding memory until it encounters a random null byte (or crashes with a read access error). That is why you are seeing std::cout output random garbage after your data.
You are also not following the Rule of 3/5/0, as you are missing a default constructor, copy and move constructors, and copy and move assignment operators.
Try this:
class Practice
{
public:
Practice(const char* a = nullptr);
Practice(const Practice &src);
Practice(Practice &&src);
~Practice();
Practice& operator=(Practice src);
const char* getString() const;
private:
char* mString;
int mSize;
};
#include "Practice.h"
#include <utility>
Practice::Practice(const char * a)
: mSize(0)
, mString(nullptr)
{
if (a)
{
while (a[mSize] != '\0')
{
++mSize;
}
}
mString = new char[mSize + 1];
for (int i = 0; i < mSize; ++i)
{
mString[i] = a[i];
}
mString[mSize] = '\0';
}
Practice::Practice(const Practice &src)
: mSize(src.mSize)
, mString(nullptr)
{
mString = new char[mSize + 1];
for (int i = 0; i < mSize; ++i)
{
mString[i] = src.mString[i];
}
mString[mSize] = '\0';
}
Practice::Practice(Practice &&src)
: mSize(src.mSize)
, mString(src.mString)
{
src.mString = nullptr;
src.mSize = 0;
}
Practice::~Practice()
{
delete[] mString;
}
Practice& Practice::operator=(Practice src)
{
std::swap(mString, src.mString);
std::swap(mSize, src.mSize);
return *this;
}
const char* Practice::getString() const
{
return mString;
}
#include <iostream>
#include "Practice.h"
int main()
{
Practice p("Hello");
std::cout << p.getString() << std::endl;
return 0;
}

unsigned char variable is not incremented

I came across one strange behaviour. In my code one variable is decremented, but not incremented and as a result my algorithm does not work. The variable name is blocksAvailable, it is defined in Chunk class, initiated with Chunk::init method, decremented with Chunk::allocate method and must be incremented with Chunk::deallocate method. So, there are just two places where this variable is mentioned - allocate and deallocate methods. In one place it gets decremented (and it works) and in other place it gets incremented and it does not work. This is the completely minimized and reproducible code:
#include <cstddef>
#include <iostream>
#include <vector>
using uchar = unsigned char;
class Chunk
{
private:
friend class FixedAllocator;
void init(size_t blockSize, uchar blocks);
void release();
void* allocate(size_t blockSize);
void deallocate(void* p, size_t blockSize);
inline bool hasBlock(void* p, size_t chunkLen) const
{
uchar * pc = static_cast<uchar*>(p);
return (pData <= pc) && (pc <= (pData + chunkLen));
}
inline bool releasable(uchar numBlocks) const
{
return blocksAvailable == numBlocks;
}
uchar* pData;
uchar firstAvailableBlock, blocksAvailable;
};
void Chunk::init(size_t blockSize, uchar blocks)
{
// for n of Ts it will allocate n * sizeof(T) memory
pData = new uchar[blockSize * blocks];
firstAvailableBlock = 0;
blocksAvailable = blocks;
uchar i = 0;
uchar* p = pData;
// used by allocate method to move forward firstAvailableBlock
for (; i != blocks; p += blockSize)
{
*p = ++i;
}
}
void Chunk::release()
{
::operator delete(pData);
}
void* Chunk::allocate(size_t blockSize)
{
if (!blocksAvailable) return 0;
// move firstAvailableBlock one block ahead
uchar* pResult = pData + firstAvailableBlock * blockSize;
firstAvailableBlock = *pResult;
--blocksAvailable;
std::cout << "blocksAvailable after allocate " << blocksAvailable << std::endl;
return pResult;
}
void Chunk::deallocate(void* p, size_t blockSize)
{
uchar* toRelease = static_cast<uchar*>(p);
// find last but one available block
firstAvailableBlock = static_cast<uchar>((toRelease - pData) / blockSize);
++blocksAvailable;
std::cout << "blocksAvailable after deallocate " << blocksAvailable << std::endl;
}
class FixedAllocator
{
private:
size_t blockSize;
uchar blocks;
using Chunks = std::vector<Chunk>;
Chunks chunks;
Chunk* allocChunk;
public:
FixedAllocator();
~FixedAllocator();
void init(size_t blockSize, size_t pageSize);
const int blockOwner(void* p) const;
void * allocate();
void deallocate(void* p);
};
FixedAllocator::FixedAllocator()
:blockSize(0),
blocks(0),
chunks(0),
allocChunk(nullptr)
{
}
FixedAllocator::~FixedAllocator()
{
Chunks::iterator it;
for (it = chunks.begin(); it != chunks.end(); ++it)
{
it->release();
}
}
void FixedAllocator::init(size_t blockSize_, size_t pageSize)
{
blockSize = blockSize_;
size_t numBlocks = pageSize / blockSize;
blocks = static_cast<uchar>(numBlocks);
}
const int FixedAllocator::blockOwner(void* p) const
{
size_t chunkLen = blocks * blockSize;
std::vector<int>::size_type i = 0, sz = chunks.size();
for (; i < sz; i++)
{
if (chunks[i].hasBlock(p, chunkLen))
{
return i;
}
}
return -1;
}
void* FixedAllocator::allocate()
{
if (!allocChunk || allocChunk->blocksAvailable == 0)
{
Chunks::iterator i = chunks.begin();
for (;;++i)
{
if (i == chunks.end())
{
// allocate memory for one more chunk
chunks.reserve(chunks.size() + 1);
Chunk newChunk;
newChunk.init(blockSize, blocks);
// add new chunk to memory pool
chunks.push_back(newChunk);
// points to new just initiated chunk
allocChunk = &chunks.back();
break;
}
if (i->blocksAvailable > 0)
{
// points to chunk with available blocks
allocChunk = &*i;
break;
}
}
}
return allocChunk->allocate(blockSize);
}
void FixedAllocator::deallocate(void* p)
{
// TODO. Optimize. Now very bruteforce and non-efficient
const int chunkPos = blockOwner(p);
if (chunkPos != -1)
{
Chunk chunk = chunks[chunkPos];
chunk.deallocate(p, blockSize);
// if chunk is releasable, release memory
if (chunk.releasable(blocks))
{
chunk.release();
chunks.erase(chunks.begin() + chunkPos);
// allocChunk may point to deleted chunk
// so, reset it
allocChunk = &chunks.back();
} else {
// there are free blocks in chunk
// so, reset allocChunk for faster future allocation
allocChunk = &chunk;
}
}
}
int main() {
FixedAllocator* alloc = new FixedAllocator();
alloc->init(4, 12);
void* p = alloc->allocate();
void* q = alloc->allocate();
void* r = alloc->allocate();
alloc->deallocate(p);
alloc->deallocate(q);
alloc->deallocate(r);
return 0;
}
As you can see, I have two debug statements in my code. One which prints blocksAvailable value after increment and one which prints its value after decrement.
But this is what I have on my screen, when I compile and run my code:
As you can see, blocksAvailable is initiated with value 3, then it gets decremented three times (three calls to allocate method), but after each decrement (call to deallocate) its value stays the same - 1. It really drives me crazy and looks like some ghost in my code. You can easily reproduce it, compile and run as simply as:
$ g++ main.cpp
$ ./a.out
I hope, someone can help me to find where this ghost appeared from.
Here is the only place in your code where you call Chunk::deallocate:
Chunk chunk = chunks[chunkPos];
chunk.deallocate(p, blockSize);
The first line makes a copy of your Chunk; the second line calls deallocate on it, which increments chunk.blocksAvailable. But chunk is just a copy of the data. Modifying it has no lasting effect.
In particular, chunks[chunkPos] is unaffected and still contains blocksAvailable = 0.