Для класса, который я беру, мы реализуем наш собственный односвязный список на С++, чтобы мы могли лучше понять, как функционирует структура данных. В настоящее время я завершил код, который проходит все тестовые случаи, но, запустив valgrind, я обнаружил, что у меня все еще есть утечки памяти.

Я реализовал код, который должен удалять каждый объект Node всякий раз, когда он удаляется из списка, но, видимо, я сделал что-то не так. Что я не понимаю в управлении памятью, чего мне не хватает?

Мой код проходит некоторые базовые тесты без утечек памяти, но при использовании более строгих тестов, предоставленных моим профессором, valgrind демонстрирует серьезные проблемы с утечками памяти.

Вот мой класс связанного списка:

template<typename T>
class LinkedList: public LinkedListInterface<T> {

    private:

        struct Node {
            Node(T val) {
                value = val;
                next = NULL;
            }
            T value;
            Node *next;
        };

        Node *head;

    public:

    LinkedList() {
        head = NULL;

    }

    ~LinkedList() {

    }

    void insertHead(T value) {
        cout << "In insertHead function" << endl;
        Node *newNode = new Node(value);
        if(head == NULL){
            head = newNode;
        }
        else {
            newNode->next = head;
            head = newNode;
        }
    }

    //don't allow duplicate values in the list. Implement later.
    void insertTail(T value) {
        cout << "In insertTail function" << endl;
        Node *newNode = new Node(value);

        if(head == NULL) {
            head = newNode;
        }
        else {
            //find last node
            Node *fakeIterator = head;
            //while what fake iterator is pointing to is not NULL then make it point to the next pointer.
            while (fakeIterator->next != NULL) {
                fakeIterator = fakeIterator->next;
            }
            //set that node's next pointer to newNode
            fakeIterator->next = newNode;
        }
    }

    void insertAfter(T value, T insertionNode) {
        cout << "In insertAfter function" << endl;
        Node *fakeIterator = head;

        while(fakeIterator != NULL) {
            if (fakeIterator->value == insertionNode) {
                Node *newNode = new Node(value);
                newNode->next = fakeIterator->next;
                fakeIterator->next = newNode;
                break;
            }
            fakeIterator = fakeIterator->next;
        }
    }

    string toString() {
        cout << "In toString function" << endl;
        stringstream ss;
        Node *fakeIterator = head;
        while (fakeIterator != NULL) {
            if (fakeIterator->next == NULL)
                ss << fakeIterator->value;
            else
                ss << fakeIterator->value << ", ";

            fakeIterator = fakeIterator->next;
        }

        return ss.str();
    }

    void remove(T value) {
        cout << "In remove function" << endl;
        if (head != NULL) {
            Node *fakeIterator = head;
            if(head->value == value) {
                Node *nodeToDelete = head;//new Node(value);
                // nodeToDelete = head;
                head = head->next;
                delete nodeToDelete;
            }
            else {
                while(fakeIterator->next != NULL) {
                    //if the value of the node after this one equals the value
                    if ( (fakeIterator->next)->value == value) {
                        //make a temp node to store the node being destroyed
                        Node *nodeToDelete = fakeIterator->next;
                        //change "next" to point to the item after the one being deleted
                        fakeIterator->next = fakeIterator->next->next;
                        //delete the node
                        delete nodeToDelete;
                        break;
                    }
                    fakeIterator = fakeIterator->next;
                }
            }
        }
    }

    void clear() {
        cout << "In clear function" << endl;
        while (head != NULL) {
            remove(head->value);
        }
    }

    T at(int index) {
        cout << "In at function" << endl;
        Node *fakeIterator = head;
        if (head == NULL) {
            throw out_of_range("list is empty");
        }
        for (int i = 0; i < index ; i++) {
            cout << "2" << endl;
            if (fakeIterator->next == NULL) {
                cout << "3" << endl;
                throw out_of_range("index does not exist");
                break;
            }
            fakeIterator = fakeIterator->next;
            cout << "4" << endl;
        }

        return fakeIterator->value;
    }

    int size() {
        cout << "In size function" << endl;
        int sizeOfList = 0;
        Node *fakeIterator = head;
        while (fakeIterator != NULL) {
            if (fakeIterator->next == NULL)
                return sizeOfList;
            else
                sizeOfList++;

            fakeIterator = fakeIterator->next;
        }
    }

};

Вот результат valgrind:

==14052== Process terminating with default action of signal 2 (SIGINT)
==14052==    at 0x57BFFE0: __read_nocancel (in /lib64/libc-2.17.so)
==14052==    by 0x574CB83: _IO_file_underflow@@GLIBC_2.2.5 (in /lib64/libc-2.17.so)
==14052==    by 0x574DD51: _IO_default_uflow (in /lib64/libc-2.17.so)
==14052==    by 0x5748729: getchar (in /lib64/libc-2.17.so)
==14052==    by 0x4024C1: main (main.cpp:88)
==14052== 
==14052== HEAP SUMMARY:
==14052==     in use at exit: 16,468 bytes in 696 blocks
==14052==   total heap usage: 2,924 allocs, 2,228 frees, 523,457 bytes allocated
==14052== 
==14052== 96 (16 direct, 80 indirect) bytes in 1 blocks are definitely lost in loss record 10 of 18
==14052==    at 0x4C29203: operator new(unsigned long) (vg_replace_malloc.c:334)
==14052==    by 0x40442F: LinkedList<int>::insertHead(int) (LinkedList.h:58)
==14052==    by 0x4034A0: void parse_instruction<int>(std::string, std::basic_ofstream<char, std::char_traits<char> >&, LinkedList<int>*) (main.cpp:101)
==14052==    by 0x4023AC: main (main.cpp:67)
==14052== 
==14052== 585 (16 direct, 569 indirect) bytes in 1 blocks are definitely lost in loss record 15 of 18
==14052==    at 0x4C29203: operator new(unsigned long) (vg_replace_malloc.c:334)
==14052==    by 0x403BEB: LinkedList<std::string>::insertHead(std::string) (LinkedList.h:58)
==14052==    by 0x402C84: void parse_instruction<std::string>(std::string, std::basic_ofstream<char, std::char_traits<char> >&, LinkedList<std::string>*) (main.cpp:101)
==14052==    by 0x402371: main (main.cpp:64)
==14052== 
==14052== 15,528 (16 direct, 15,512 indirect) bytes in 1 blocks are definitely lost in loss record 18 of 18
==14052==    at 0x4C29203: operator new(unsigned long) (vg_replace_malloc.c:334)
==14052==    by 0x403DE3: LinkedList<std::string>::insertAfter(std::string, std::string) (LinkedList.h:94)
==14052==    by 0x402DF6: void parse_instruction<std::string>(std::string, std::basic_ofstream<char, std::char_traits<char> >&, LinkedList<std::string>*) (main.cpp:111)
==14052==    by 0x402371: main (main.cpp:64)
==14052== 
==14052== LEAK SUMMARY:
==14052==    definitely lost: 48 bytes in 3 blocks
==14052==    indirectly lost: 16,161 bytes in 687 blocks
==14052==      possibly lost: 0 bytes in 0 blocks
==14052==    still reachable: 259 bytes in 6 blocks
==14052==                       of which reachable via heuristic:
==14052==                         stdstring          : 259 bytes in 6 blocks
==14052==         suppressed: 0 bytes in 0 blocks
==14052== Reachable blocks (those to which a pointer was found) are not shown.
==14052== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==14052== 
==14052== For counts of detected and suppressed errors, rerun with: -v
==14052== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
1
Sam Sabin 14 Ноя 2019 в 20:53
1
Комментарии не для расширенного обсуждения; этот разговор был перенесено в чат.
 – 
Samuel Liew
15 Ноя 2019 в 15:55

1 ответ

Во-первых, я хотел бы поощрить вас в обучении работе со структурами данных. Я думаю, что это лучший способ научиться программированию, от указателей и памяти до алгоритмов «разделяй и властвуй» и многопоточных приложений.

Теперь я вижу, что ваш код не следует строго рекомендациям минимального воспроизводимого примера Stackoverflow, так как он содержит методы, которые не связанных с утечкой, таких как «at», «toString» и «size», и не дает случая, который можно было бы использовать для воспроизведения вашего вывода valgrind. Я рекомендую вам внимательно следить за ними, потому что в противном случае вы можете заработать отрицательные голоса в будущем. В моем случае я воспользуюсь этим, чтобы попытаться помочь вам улучшить вашу реализацию LinkedList.

Основная проблема, которую я вижу, заключается в том, что деструктор LinkedList ничего не делает (только освобождает память, используемую сам по себе, а не своими узлами), поэтому, если ваша программа завершается после добавления одного элемента, скажем:

int main() {    
  auto l = new LinkedList<int>();
  l->insertHead(1);
  // l->remove(1);
  delete l;
}

Память, соответствующая 1 узлу (голове), будет утеряна. Реализация, которую я бы рекомендовал:

~LinkedList() {
  while (head != NULL) {
    Node *nodeToDelete = head;
    head = head->next;
    delete nodeToDelete;
  }
}

Эта реализация не должна дать вам никакой утечки, если вы вызываете удаление для каждого созданного вами LinkedList, или, если вы чувствуете себя готовым, вы можете использовать интеллектуальные указатели, которые вызывают удаление для ваших LinkedList, когда они выходят за рамки, как если бы вы не использовали указатели. Но с ними вы также получите утечки без деструктора, который не освобождает каждый узел LinkedList должным образом. Я надеюсь, что это поможет вам, ниже я дам вам другие рекомендации на случай, если вы хотите узнать больше и заинтересованы в том, чтобы все ваши методы LinkedList работали так, как ожидает ваш профессор. Удачи.


С другой стороны, кажется, что ваш метод size не считает голову, если она не равна нулю. Я бы упростил это так:

int size() {
  cout << "In size function" << endl;
  int sizeOfList = 0;
  Node *fakeIterator = head;
  while (fakeIterator != NULL) {
    ++sizeOfList;
    fakeIterator = fakeIterator->next;
  }
  return sizeOfList;
}

Бонус умного указателя:

#include <memory>

int main() {    
  auto l = make_unique<LinkedList<int>>();
  l->insertHead(1);
}

Выход Valgrind:

==3288== Memcheck, a memory error detector
==3288== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3288== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==3288== Command: ./run
==3288== 
In insertHead function
==3288== 
==3288== HEAP SUMMARY:
==3288==     in use at exit: 0 bytes in 0 blocks
==3288==   total heap usage: 4 allocs, 4 frees, 73,760 bytes allocated
==3288== 
==3288== All heap blocks were freed -- no leaks are possible
==3288== 
==3288== For counts of detected and suppressed errors, rerun with: -v
==3288== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
1
Community 20 Июн 2020 в 12:12