Объект, созданный в цикле for, имеет тот же адрес

#c

#c #указатели #память #ошибка сегментации

Вопрос:

Я пытаюсь создать собственный класс list, который имеет фиксированный размер и может хранить целые числа. Просто для учебных целей.

Вот как я это делаю: у меня есть структура с именем Item, которая содержит данные (целое число для хранения) и указатель на следующий элемент в списке. Когда список инициализируется, я сначала добавляю в список x количество пустых элементов.

Адрес элемента, объявленного на итерации (n-1), сохраняется в буфере, так что адрес элемента на итерации n может быть установлен как следующий для элемента на предыдущей итерации (n-1). Это позволяет связать все элементы друг с другом.

И адрес самого первого элемента также сохраняется как точка доступа для всего списка позже в коде.

Но на самом деле это не работает; это мой код:

 #include <iostream>

class List {
    public:
    //Item-object needed to store
    //elements
    struct Item {
        int data = 0;
        Item* next = nullptr;
    };

    //Constructor
    //creates a list of fixed size
    List(int size) {
        //Filling up with empty items
        for (int i = 0; i < size; i  ) {
            Item item;

            //Storing iterator in item (just for testing purposes)
            item.data = i;

            //If first item,
            //store start-address
            //set buffer to start address
            if (i == 0) 
                this->start = amp;item;

            //Setting current item as nextptr
            //for previous item in buffer 
            if (i > 0)
                this->buffer->next = amp;item;

            //Storing current address in buffer
            this->buffer = amp;item;

            //Outputting address and value (just for testing purposes)
            //std::cout << "Address: " << amp;item << " -> " << item.data << std::endl;
        }
    }

    Item* getFirstItemAddress() {
        return this->start;
    }

    private:
    //Holding address of first item
    Item* start = nullptr;

    //Buffer holding temporary address
    Item* buffer = nullptr;

};

int main() {
    List list(5);

    //Printing out
    List::Item* current = list.getFirstItemAddress();
    while (current->next) {
        std::cout << current->data << std::endl;
        current = current->next;
    }

    return 0;
}
  

Это вывод:

 Testing output:
1168769696
-1064971727
Segmentation fault
  

Однако, когда я раскомментирую тестовую строку 37, это вывод:

 Address: 0x7ffe54015cf0 -> 0
Address: 0x7ffe54015cf0 -> 1
Address: 0x7ffe54015cf0 -> 2
Address: 0x7ffe54015cf0 -> 3
Address: 0x7ffe54015cf0 -> 4

Testing output:
1648675776
1648572376
1646105840
1226279756
Segmentation fault
  

Во-первых, я не понимаю, как вывод может так резко изменить вывод «тестового вывода»…

В любом случае, информация о сбое сегментации:

 (gdb) run
Starting program: /home/niel/Desktop/listTest/main 
Address: 0x7fffffffe0a0 -> 0
Address: 0x7fffffffe0a0 -> 1
Address: 0x7fffffffe0a0 -> 2
Address: 0x7fffffffe0a0 -> 3
Address: 0x7fffffffe0a0 -> 4

Testing output:
-136467520
-136570920
-139037456
1226279756

Program received signal SIGSEGV, Segmentation fault.
0x000000000040092e in main () at main.cpp:62
62              std::cout << current->data << std::endl;
  

Понятия не имею, почему current-> data выдает ошибку сегментации, поскольку я предоставил данные каждому элементу!

Но что меня больше всего беспокоит, так это:

  Address: 0x7fffffffe0a0 -> 0
    Address: 0x7fffffffe0a0 -> 1
    Address: 0x7fffffffe0a0 -> 2
    Address: 0x7fffffffe0a0 -> 3
    Address: 0x7fffffffe0a0 -> 4
  

Каждый элемент имеет один и тот же адрес, конечно, это не то, что я имел в виду. Каждый элемент, созданный на итерациях, должен быть другим элементом с другим адресом.
Имеет ли это какое-то отношение к тому, что элемент объявлен локальным, и по этой причине объект хранится в стеке, а не в свободном пространстве? Я попытался использовать ключевое слово ‘new’, но это не сработало!

TL; DR 1) Почему я получаю ошибку сегментации 2) Как исправить, чтобы каждый элемент, созданный в List(), имел отдельный адрес?

Комментарии:

1. Используется new для выделения памяти для Item . Почему вы ожидаете, что локальная переменная alllocated в локальном хранилище будет иметь другой адрес на каждой итерации цикла?

2. Сохранение amp;item для последующего использования бессмысленно, поскольку item время жизни заканчивается итерацией, на которой оно было создано. Последующее разыменование его не определено.

Ответ №1:

Проблема

В вашем конструкторе списка вы помещаете в свой список адрес объекта item , который является локальным для цикла и уничтожается в конце каждой итерации.

Решение

Вы должны создать new объект в свободном хранилище, чтобы он пережил итерацию и конец конструктора. Как вы учитесь, я сохраняю простоту и использую необработанные указатели:

 List(int size) {
    for (int i = 0; i < size; i  ) {
        Item *pitem = new Item;  
        pitem->data = i;  

        if (i == 0) 
            start = pitem;
        if (i > 0)
            buffer->next = pitem;

        //Storing current address in buffer
        buffer = pitem;

        //Outputting address and value (just for testing purposes)
        //std::cout << "Address: " << pitem << " -> " << item->data << std::endl;
    }
}
  

Но тогда ваш код будет пропускать память. Если у вас есть такой конструктор, вам также понадобится конструктор копирования, оператор присваивания и деструктор (правило 3)

Комментарии:

1. Я уже думал о чем-то подобном и пытался использовать ‘new’, но получил ошибки. Благодаря вам я знаю, понимаю это на 100%! Спасибо!

Ответ №2:

Item item; является переменной, выделенной стеком, что означает, что когда программа выходит из текущей области видимости (в данном случае для итерации цикла), она будет освобождена. Адрес памяти один и тот же, потому что компилятор каждый раз выделяет его в одном и том же месте. Ваша программа выходит из строя, потому что вы пытаетесь разыменовать указатель, указывающий на ничто.

Вы должны динамически распределять каждый Item из них в куче через a new Item; — Но будьте осторожны, вы ДОЛЖНЫ вызывать delete каждую из выделяемых вами переменных, иначе у вас будет утечка памяти.

Комментарии:

1. Примечание: выделенное локальное хранилище является более правильным термином, чем stack .