2

Sequel to: array wrapper corrupts stack

The project:

I am working on a std::vector replacement.

The error:

I get a heap corruption error whenever I attempt to delete a temporary array I am creating in order to store the copied elements of another array that I delete in order to reallocate it when my array gets resized. (It seems that if I try to assign one array to another, I actually end up assigning the pointer to the array to the other array instead of copying the elements. Truly crazy stuff).

I read online that this error may actually come from the part where I actually interact with the array, but the error only pops out when I attempt to delete it.

Where I interact with the array is the function _tempcpy. I pass a pointer to a source and destination array and copy each element from the source to the destination. I struggled a bit with making sure that the array would start from 0 and not 1 and in the process I messed a bit too much with the element_count member, so it may be that i am somehow writing outside of bounds and I have stared so much at the code I can't see the issue.

The code:

template<class T> class dyn_arr
{
public:

    dyn_arr(void)
    {
        this->array         = {};
        this->element_count = {};
    }

    ~dyn_arr(void)
    {
        this->dealloc();
    }

    bool alloc(unsigned int element_count)
    {
        if (0 == element_count)
        {
            element_count = 1;
        }

        if (this->array != nullptr)
        {
            T* temp = new T[this->element_count];

            if (false == this->_tempcpy(&this->array, &temp, this->element_count))
            {
                return false;
            }

            delete[] this->array;
            this->array = new T[this->element_count];

            if (false == this->_tempcpy(&temp, &this->array, this->element_count))
            {
                return false;
            }

            delete[] temp;

            if (nullptr != this->array)
            {
                return true;
            }
        }
        else
        {
            this->array = new T[this->element_count];

            if (nullptr != this->array)
            {
                return true;
            }
        }

        return false;
    }

    bool dealloc(void)
    {
        if (nullptr == this->array)
        {
            return false;
        }

        delete[] this->array;

        return true;
    }

    bool add(T Object)
    {
        if (0 == Object)
        {
            return false;
        }

        if (true == this->alloc(this->element_count))
        {
            this->array[this->element_count] = Object;

            ++this->element_count;

            return true;
        }

        return false;
    }

    T get(unsigned int index)
    {
        if (index > this->element_count)
        {
            return T{};
        }

        return this->array[index];
    }

    unsigned int get_count(void)
    {
        return this->element_count;
    }

private:

    bool _tempcpy(T** src, T** dest, unsigned int count)
    {
        if ((nullptr == src) || (nullptr == dest) || (0 == count))
        {
            return false;
        }

        for (unsigned int i = 0; i < count; ++i)
        {
            *dest[i] = *src[i];
        }

        return true;
    }

    T* array;
    unsigned int element_count;
};

int main()
{
    dyn_arr<int> pNr = {};
    pNr.add(1);
    pNr.add(2);
    pNr.add(3);

    for (int i = 0; i < pNr.get_count(); ++i)
    {
        printf("%d\n", pNr.get(i));
    }

    getchar();
    return 0;
}
trincot
  • 317,000
  • 35
  • 244
  • 286
Hjkl
  • 59
  • 6
  • 1
    Any particular reason you are reinventing the wheel with a replacement for vector? – Tony Tannous Jun 13 '20 at 21:02
  • 1
    I want to both learn more, have more control over how it works and I just have fun writing it – Hjkl Jun 13 '20 at 21:04
  • `if (nullptr == this->array) { return false; } delete[] this->array;` - that whole `if` block is pointless. `delete[] nullptr;` is a noop - guaranteed to do nothing. – Jesper Juhl Jun 13 '20 at 21:09
  • Not the cause of your problem (yet) but see https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three#:~:text=In%20that%20case%2C%20remember%20the,declare%20all%20three%20of%20them. – Alan Birtles Jun 13 '20 at 21:09
  • `alloc` doesn't make `this->array` any bigger – Alan Birtles Jun 13 '20 at 21:12
  • 1
    Why all the explicit `this->` all over the place? You don't need that and it makes the code harder to read. – Jesper Juhl Jun 13 '20 at 21:13
  • @JesperJuhl I just like to have return values to see if an operation succeeded or not, but yeah ATM it is pointless ###AlanBirtles I definitely am going to implement those as well as multiple operators afterwards to increase my wrapper's functionality – Hjkl Jun 13 '20 at 21:13
  • @AlanBirtles I increase it in the "add" function ###JesperJuhl it makes it easier for me. Also, one of the member names is "array" which may be confusing for the compiler perhaps since array is also a type – Hjkl Jun 13 '20 at 21:15
  • Your `alloc` function never references the `element_count` parameter. Once you've fixed that, pass it `this->element_count + 1` in `add` and you will be most of the way there. – Paul Sanders Jun 13 '20 at 21:20
  • I like yoda conditions :-) The prog simply has invalid logic. Debugging will help with any standard environment. In addition valgrind will help too but is not needed while reading that code. Once you go step by step through your code you will see that it simply did not (re)allocate memory and write to non allocated array. – Klaus Jun 13 '20 at 21:23
  • @Klaus unfortunately I cannot run valgrind (afaik) on my Windows machine, but indeed I am very prone to having bad logic in my code. I just noticed that alloc calls `new T[0]` the first time its called, so I fixed that and now my Heap Corruption turned into a read access violation in the _tempcpy function which I am trying to elucidate – Hjkl Jun 13 '20 at 21:32
  • I have updated my `_tempcpy` code to erase those useless troubling double pointers and i replaced the count parameter with just calculating the number of elements in the destination array (`sizeof(dest) / sizeof(*dest)`) so i make sure i do not mess it up and yet I once again get a heap corruption error when i copy the very first element in `_tempcpy`. I really would like to smash my computer until it turns into sand right now, can anyone tell me what could be wrong? @Klaus I do not see what you are telling me, man, the allocation of temp seems to go well – Hjkl Jun 13 '20 at 21:53
  • Aren't you copying object twice when adding? First passing by value and again doing the assignment? Just pass object by reference and then do the assignment? – user997112 Jun 13 '20 at 22:11
  • You mean the function prototype should be `Add(T& obj)`? In that case I wouldn't be able to just do stuff like `...->Add(100)`, I would have to define the integer before, no? – Hjkl Jun 13 '20 at 22:18

1 Answers1

0

In your alloc function there lie a few problems, such as:

if (0 == element_count)
{
        element_count = 1;
}

is unnecessary. Instead, you can just do a +1 where necessary which is almost everywhere except the temp dynamic array.

bool alloc(...)
{
    this->array = new T[this->element_count];
    //...
    else
    {
       this->array = new T[this->element_count];  
    }
}

should be

    this->array = new T[this->element_count + 1];
    //...
    else
    {
       this->array = new T[this->element_count + 1];  
    }

this will fix the problems with allocation. That leaves us with _tempcpy() which fails because instead of trying to get the next element of the underlying array, it tries to do that with the double ptr itself. Read about operator precedence rules. Fixed version:

    bool _tempcpy(T** src, T** dest, unsigned int count)
    {
       //....
        for (unsigned int i = 0; i < count; ++i)
        {
            (*dest)[i] = (*src)[i];
        }
        //....
    }

However, I am unsure as to why double ptr is needed in this function in the first place. Just use single pointers. Also dest should be const as it is not changing in this function. It gives the reader of the function a clear idea of which parameters will change inside the function and which will not:

bool _tempcpy(T* src, const T* dest, unsigned int count) {...}

same could be applied to other parts of the class.

Additionally, in C++

dyn_arr(void)

We don't do this. There's no need to explicitly write void. According to the C++ standard:

8.3.5 Functions [dcl.fct] ... A parameter list consisting of a single unnamed parameter of non-dependent type void is equivalent to an empty parameter list.

Waqar
  • 8,558
  • 4
  • 35
  • 43
  • damn dude. I went crazy changing stuff in this code continuously and with just 2 fixed you made it work. crazy shit... thanks a lot. i had already changed the double ptr into a single pointer as youre right, it does not really make sense and is useless. also you suggest i use const for parameteres ill only read, which is exactly why i write void in functions with no parameters – Hjkl Jun 13 '20 at 22:14
  • I had to spend some 30 minutes for those 2 changes. Writing `void` in function parameters is C, not C++. It is redundant to write it. – Waqar Jun 13 '20 at 22:17
  • 1
    Thats some dedication, bro. I will keep your suggestions in mind while I expand the wrapper now it finally works! – Hjkl Jun 13 '20 at 22:20