обертка массива портит стек

мой проект представляет собой оболочку динамического массива, например std::vector. вот как это работает:

  • при добавлении нового элемента память либо выделяется (malloc), если она 0, либо перераспределяется с новым размером (realloc), если она не 0. размер - количество элементов * размер типа

  • при получении уже добавленного элемента вычисляю адрес, умножая его индекс на размер типа и прибавляя к адресу, по которому выделена память

ПРИМЕЧАНИЕ: я сам пишу и читаю память без каких-либо функций, таких как memcpy или memset. это необходимо для моего проекта. у меня должна быть возможность сделать это, поэтому, если вы можете, не упоминайте об этом (если я не реализовал это неправильно, в этом случае, пожалуйста, упомяните об этом)

когда я пытаюсь прочитать добавленный элемент с помощью функции get(int index), я получаю либо ошибку "стек вокруг переменной был поврежден", либо ошибку "нарушение доступа для чтения", в зависимости от того, как я пытаюсь это сделать.

я немного почитал в Интернете и обнаружил, что, возможно, каким-то образом испортил кучу с помощью malloc. также читал, что я мог узнать, где ошибка с чем-то под названием «valgrind», но, похоже, это доступно только для Linux, а я использую Windows.

вот мой код (он переписан, все проверки ошибок удалены, чтобы сделать его меньше). место, где я получаю сообщение об ошибке, комментируется:

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

person Hjkl    schedule 13.06.2020    source источник
comment
Комментарии не для расширенного обсуждения; этот разговор был перемещен в чат.   -  person Samuel Liew♦    schedule 14.06.2020


Ответы (1)


Давайте разберем код и потихоньку исправим. Начиная с конструктора, все должно быть инициализировано. Очень важно, чтобы вы инициализировали свои переменные как можно скорее:

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

Теперь давайте посмотрим на метод add. Что он должен делать? Добавьте элемент в контейнер и сохраните уже существующие. Что он делает прямо сейчас? Посмотрим:

  1. Создание временного указателя void*.
void* end_temp = 0;
  1. Проверяя, есть ли end != nullptr, мы присваиваем end end_temp.
if (this->end) { end_temp = this->end; }
  1. Выделите память и увеличьте elemc (я предполагаю, что это количество элементов)
if (true == this->alloc(++this->elemc))
  1. Следующий?? Я не уверена. Я также не уверен, какое это имеет отношение к простой задаче добавления элемента в контейнер:
{
     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;
    }

Давайте упростим и сделаем именно то, что хотим, то есть добавим элемент:

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

alloc() больше не принимает аргумент, потому что всегда увеличивается на 1. size — это количество элементов в контейнере. Мы просто присваиваем элементу последний индекс и увеличиваем размер на 1.

Давайте посмотрим get() сейчас. Что нужно сделать? Получить элемент по индексу. Напишем просто:

T get(int i) const
{
    if (i < size)
        return start[i];
}
person Waqar    schedule 13.06.2020
comment
Я действительно сильно усложнил. Код в add был написан с учетом копирования функции memcpy, поэтому я попытался пройти байт за байтом и скопировать каждый байт из источника в место назначения, но, как я позже узнал, я переделывал байт за байтом. Что касается инициализации, разве в настоящее время компилятор не инициализирует все автоматически? - person Hjkl; 14.06.2020
comment
просто помните, что для байт за байтом вы всегда должны использовать char*. reinterpret_cast<> следует использовать только с char*, почти во всем остальном это разрушит всю вашу программу. Обратитесь к этому: gist.github.com/shafik/848ae25ee209f698763cffee272a58f8 и stackoverflow.com/questions/98650/ - person Waqar; 14.06.2020
comment
Нет, только глобальные переменные и статические переменные инициализируются 0, если они являются собственными типами, такими как int/long. Все остальное нужно инициализировать, локальные переменные, члены класса - person Waqar; 14.06.2020
comment
самый простой memcpy - просто копировать байт за байтом. см.: github.com/gcc-mirror/gcc/blob/ мастер/libgcc/memcpy.c - person Waqar; 14.06.2020
comment
Я вижу сейчас. Я лично мало что знаю об этом UB, ссылки, которые вы мне дали, больше говорят о том, как строка или несколько кода могут быть неверно истолкованы компилятором и вызвать UB, а не о том, что невозможно сделать это правильно, используя небезопасный код. (или, по крайней мере, это то, что я получаю от этого). И да, благодаря churill я понял, как я испортил свой отключенный клон memcpy. Большое спасибо за помощь, бро! - person Hjkl; 14.06.2020
comment
Возможно, сейчас вы не все понимаете, но продолжайте время от времени возвращаться к этим постам, и каждый раз вы будете понимать что-то большее. - person Waqar; 14.06.2020
comment
UB просто означает неопределенное поведение. Что-то, с чем никто не знает, что делать. Так что компилятор сделает что угодно. Он может работать правильно, или сбой или что-то еще. Это не определено. Есть только одна вещь, которую можно сделать с UB: исправить это. - person Waqar; 14.06.2020
comment
Говоря о UB: не все пути обновленного get возвращают что-то, что является UB. Если индекс выходит за пределы, вы должны выдать какое-то исключение. - person churill; 14.06.2020