0

my project is a dynamic array wrapper like std::vector. this is how it works:

  • when adding a new element, the memory is either allocated (malloc), if it is 0, or reallocated with a new size (realloc), if it is not 0. the size is the number of elements * size of type

  • when getting an already added element, i calculate the address by multiplying its index by the size of the type and adding it to the address at which the memory is allocated

NOTE: i write and read the memory myself with no function like memcpy or memset. this is required for my project. it should be possible for me to do this so if you could, do not mention it (unless i implemented it wrong, in which case, please do mention it)

when i try to read in an added element with the get(int index) function, i either get a "stack around variable was corrupted" or "read access violation" error, depending on how I try to do it.

i read a bit online and found i may have corrupted the heap somehow with malloc. also read i could find out where the error is with something called "valgrind", but it seems to only be available for linux, and i use windows.

here is my code (its rewritten, all error checks were removed to make it smaller). the place where i get the error is commented:

template<class T>
class darr
{
public:
    darr(void) {}
    ~darr(void) {
        erase(); dealloc();
    }

    bool alloc(int elemc) {
        this->elemc = elemc;
        this->size = (elemc * sizeof(T));
        this->end = (this->start + this->size);

        if (this->start)
        {
            this->start = (T*)(realloc(this->start, this->size));

            if (this->start)
            {
                this->end = (this->start + this->size);
                return true;
            }
        }
        else
        {
            this->start = (T*)(malloc(this->size));

            if (this->start)
            {
                this->end = (this->start + this->size);
                return true;
            }
        }

        return false;
    }

    bool erase(void)
    {
        for (int i = 0; i <= this->size; ++i)
        {
            *(unsigned long*)(this->start + i) = 0;
        }

        return true;
    }

    bool dealloc(void)
    {
        free(this->start);

        return true;
    }

    bool add(T obj)
    {
        void* end_temp = 0;

        if (this->end) { end_temp = this->end; }

        if (true == this->alloc(++this->elemc))
        {
            end_temp = this->end;

            for (int i = 0; i <= sizeof(obj); ++i)
            {
                *(unsigned long*)((unsigned long)(end_temp)+i) = *(unsigned long*)((unsigned long)(&obj) + i);
            }
        }

        return true;
    }

    T get(int i)
    {
        unsigned long siz = sizeof(T);
        void* i_addr = this->start + (i * siz);

        //T tempobj = 0;
        T* tempobj = (T*)(malloc(sizeof(T)));
        // without malloc - stack around var corrupted (happnens at last index in for loop, no matter what index it is)
        // with malloc - read access violation
        for (int i = 0; i <= siz; ++i)
        {
            *(unsigned long*)((unsigned long)(&tempobj)+i) = *(unsigned long*)((unsigned long)(i_addr)+i);
        }

        return *tempobj;
    }
private:
    T * start;
    void* end;
    int elemc, size;
};
trincot
  • 317,000
  • 35
  • 244
  • 286
Hjkl
  • 59
  • 6
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/215920/discussion-on-question-by-hjkl-array-wrapper-corrupts-stack). – Samuel Liew Jun 14 '20 at 03:38

1 Answers1

2

Lets take the code apart and fix it slowly. Starting with the constructor, everything should be initialized. It is very important that you initialize your variables as soon as possible:

    darr() {
        start = nullptr;
        end = nullptr;
        elemc = 0;
        size = 0;
    }

Now lets look at the method add. What should it do? Add an element to the container and retain the ones already existing. What is it doing right now? Lets see:

  1. Creating a temporary void* pointer.
void* end_temp = 0;
  1. Checking if end != nullptr, we assign end to end_temp.
if (this->end) { end_temp = this->end; }
  1. Allocate memory and increasing the elemc (which I assume is element count)
if (true == this->alloc(++this->elemc))
  1. Next?? I am not sure. I am also not sure what has this got to do with a simple task of adding the element to a container:
{
     end_temp = this->end;

            for (int i = 0; i <= sizeof(obj); ++i)
            {
                *(unsigned long*)((unsigned long)(end_temp)+i) = *(unsigned long*)((unsigned long)(&obj) + i);
            }
        }

        return true;
    }

Let's simplify and do just what we want, that is, add the element:

        if (true == this->alloc())
        {
            start[size] = obj;
            this->size++;
        }

alloc() doesn't take an argument anymore because it is always increasing by 1. size is the number of elements in the container. We just assign the element to the last index, and increment the size by 1.

Let's see get() now. What does it need to do? Get the element at an index. Let's write it simply:

T get(int i) const
{
    if (i < size)
        return start[i];
}
Waqar
  • 8,558
  • 4
  • 35
  • 43
  • I indeed overcomplicated it badly. The code in `add` was written with "copying the memcpy function" in mind, so i tried to go byte by byte and copy each byte from the source to the dest, but i fked up the "byte by byte" part as I later learned. Regarding initialization, isn't everything initialized automatically by the compiler nowadays? – Hjkl Jun 13 '20 at 23:35
  • 1
    just remember, for byte by byte you should always use `char*`. `reinterpret_cast<>` should only ever be used with `char*`, for almost everything else it will ruin your whole program. Refer to this: https://gist.github.com/shafik/848ae25ee209f698763cffee272a58f8 and https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule – Waqar Jun 13 '20 at 23:38
  • 2
    No, only global variables and static variables are initialized to 0 if they are native types like `int` / `long`. Everything else needs to be initialized, local variables, class members – Waqar Jun 13 '20 at 23:40
  • simplest `memcpy` is to just copy byte by byte. see: https://github.com/gcc-mirror/gcc/blob/master/libgcc/memcpy.c – Waqar Jun 13 '20 at 23:43
  • 1
    I see now. I don't personally know much of this UB thing, the links you gave me talk more about how a line or more of code could be misinterpreted by the compiler and cause UB, not that it is impossible to get it right by using unsafe code (or at least it's what I'm getting from it). And yeah, thanks to churill I realized how I was messing up my disabled memcpy clone. Thanks a lot for your help, bro! – Hjkl Jun 13 '20 at 23:53
  • 1
    You might not understand everything now, but keep returning to these posts from time to time and everytime you will understand something more. – Waqar Jun 13 '20 at 23:59
  • UB just means undefined behavior. Something about which no one knows what to do. So the Compiler will do anything. It may run it correctly, or crash or anything else. It is undefined. There's only one thing that can be done about UB: Fix it. – Waqar Jun 14 '20 at 00:00
  • Talking about UB: not all paths of the updated `get` return something, which is UB. If the index is out of bounds you should throw some exception. – Lukas-T Jun 14 '20 at 05:48