-1

I am working with doubly linked list. Every function operates well but at the end of main(), it stalls few seconds and return an unexpected random value.

At first I thought it was caused by the report() function, thus I put one more add() function at the end but nothing got fixed. I doubt it is a memory deallocating problem, but I don't see where some object got pre-deallocated. (Compiled using Code::Blocks 17.12).

Here is my .cpp file (all in one):

#include <iostream>
using namespace std;

typedef struct element {
    element(){
        data = 0;
        next = 0;
        prev = 0;
    }
    ~element(){
        delete next;
        delete prev;
        cout << "element destructed" << endl;
    }
    int data;
    element* next;
    element* prev;
} elem;

typedef struct doublylinkedlist{
    doublylinkedlist(){
        head = 0; tail = 0;
    }
    ~doublylinkedlist(){
        while(head!=0) {
        head = head->next;
        delete head->prev;
    }
        delete tail;
        cout << "list destructed" << endl;
    }
    elem* head;
    elem* tail;
} doublyll;

doublyll ls;

void add(){
    elem* temp = new elem;
    cout << "Enter an integer: ";
    cin >> temp->data;

    if(ls.head == 0) {//empty
        ls.head = new elem;
        ls.head = temp;

    } else{
        if(ls.tail == 0){  //1-item list
            ls.tail = new elem;
            ls.tail = temp;
            ls.head->next = ls.tail;
            ls.tail->prev = ls.head;
        }
        else{
            temp->prev = ls.tail;
            ls.tail->next = temp;
            ls.tail = temp;
        }
    }
}

void report(){
    if(ls.head == 0) cout << "List is empty!" << endl;
    else{
        elem *temp = ls.head;
        do{
            cout << temp->data << endl;
            temp = temp->next;
        } while (temp != 0);
    }
}



int main(){
    report();
    add();
    add();
    add();
    report();
    add();
    return 0;
}

Could someone point out where the error comes from and how to fix it? I want the main() not to stall and return 0 as usual, not to the opposite. This is the program when executed, this is my build message

xceeded
  • 45
  • 6
  • Some elements will deleted multiple times by `doublylinkedlist` and by `element`, but there should be some more problems because removing `delete` statements from `element` destructor didn't get rid of the Segmentation Fauit. – MikeCAT Oct 13 '19 at 15:08
  • `ls.head = new elem; ls.head = temp;` Oh, memory leak... (same mistake is also done using `ls.tail`) – MikeCAT Oct 13 '19 at 15:09
  • This doesn't address the question, but in C++ you don't have to do that `typedef struct { ... } elem;` dance. `struct elem { ... };` works just fine. Or just use `struct element { ... };` and forget the abbreviation. – Pete Becker Oct 13 '19 at 16:18
  • `0xC00000FD` isn't a random number, it means there was a stack overflow – Alan Birtles Oct 13 '19 at 16:35
  • @AlanBirtles thanks a lot for telling me that. It turns out that return value `0xC00000FD` is caused by accessing a null pointer, due to my dumb code: `delete head->prev;` in the doublyll deconstructor. – xceeded Oct 14 '19 at 12:33
  • Your code would never reach `delete head->prev`, see my answer for more details – Alan Birtles Oct 14 '19 at 12:40

2 Answers2

0

First point: The elements will be deallocated by the class doublylinkedlist, so deallocating elements in the class element will cause double-deallocation. Therefore, you should remove two delete statements from the destructior of the lass element.

    ~element(){
        /* remove them */
        //delete next;
        //delete prev;
        cout << "element destructed" << endl;
    }

Second point: In the destructor of doublylinkedlist, head->prev is read after head = head->next; without checking if head is NULL. head can be NULL by the assignment, so it should be checked.

    ~doublylinkedlist(){
        while(head!=0) {
            head = head->next;
            if (head!=0) /* add this */
                delete head->prev;
        }
        delete tail;
        cout << "list destructed" << endl;
    }

The last element will be deallocated by delete tail;, so this code looks tricky but should be OK.

Extra point: These code segments

        ls.head = new elem;
        ls.head = temp;

and

            ls.tail = new elem;
            ls.tail = temp;

are causing memory leaks by allocating elements and throwing them right away. You should remove the extra allocations.

        /* remove this */
        //ls.head = new elem;
        ls.head = temp;

and

            /* remove this */
            //ls.tail = new elem;
            ls.tail = temp;
MikeCAT
  • 73,922
  • 11
  • 45
  • 70
  • About your extra point, this is how I thought: temp was declared inside the add() function, so it would be deconstructed by the ~elem() deconstructor before quiting the add() function, so I allocated ls.head and ls.tail since their scopes are global. Did I get it wrong? – xceeded Oct 14 '19 at 12:44
  • @xceeded `add()` function is outside the `element` class, so `~elem()` deconstructor will not affect local variables in the `add()`function. The pointer `temp` will be freed on exiting `add()` function, but the space whose pointer is assigned to that won't be freed at that timing, `ls.head` and `ls.tail` are already allocated as part of `ls`. `ls.head = new elem;` is not allocating `ls.head` but allocating another space and assigning its place to `ls.head`. – MikeCAT Oct 20 '19 at 12:52
  • Wait, if A: "`ls.head` points to `temp`", B: "`temp` gets deleted on exiting `add()`" and C:"A program that dereferences a pointer after the object is deleted can have unpredictable results or crash" ([docs.microsoft](https://learn.microsoft.com/en-us/cpp/cpp/delete-operator-cpp?view=vs-2019)) then why does my program still works? – xceeded Oct 22 '19 at 09:17
  • `temp` gets deleted on exited `add()`, but **what is pointed at** by `temp` (what is created via `new elem`) don't get deleted for that. `ls.head` points to "what is pointed at by `temp`", not `temp` itself. – MikeCAT Oct 23 '19 at 22:30
  • Thank you for being nice and straight, I think I get it now. – xceeded Oct 24 '19 at 11:42
0

Unless you are using std::shared_ptr or similar constructs each object needs to have one other object which is it's owner and is responsible for deallocating it. Your code needs to have clear semantics for transferring ownership (e.g. a function createNode() would expect its caller to destroy the node).

In your code nodes are both deleted by the list and by each element. This means everything gets deleted twice (or more). In your particular case this is the sequence of events on destruction of doublylinkedlist:

  1. doublylinkedlist deletes its first element.
  2. The destructor of the first element deletes its previous element, this is null so has no effect
  3. The destructor of the first element deletes its next element (the second element).
  4. The destructor of the second element deletes its previous element (the first element)
  5. The destructor of the first element deletes its previous element, this is null so has no effect
  6. The destructor of the first element deletes its next element (the second element).

This infinite loop eventually causes a stack overflow. Note that this isn't guaranteed to be the exact sequence of events as deleting an object twice is undefined behaviour so potentially anything could happen.

The simple fix is to remove the element destructor and have the list be responsible for the lifetime of all elements. You should also modify your doublylinkedlist destructor as it will attempt to dereference a null pointer on the last element, you also don't need to delete tail as it should have already been deleted. E.g:

~doublylinkedlist(){
    while(head!=0) {
        auto temp = head;
        head = head->next;
        delete temp;
    }
}

You shoudl also make sure you obey the rule of three/five). One way of doing this is to make use of smart pointers, for example using unique_ptrs your code could look like this:

#include <iostream>
#include <memory>
using namespace std;

typedef struct element {
    element() {
        data = 0;
        next = nullptr;
        prev = nullptr;
    }
    ~element() {
        cout << "element destructed" << endl;
    }
    int data;
    std::unique_ptr< element > next;
    element* prev;
} elem;

typedef struct doublylinkedlist {
    doublylinkedlist() {
        head = 0; tail = 0;
    }
    ~doublylinkedlist() {
        std::cout << "list destructed\n";
    }

    std::unique_ptr< elem > head;
    elem* tail;
} doublyll;

doublyll ls;

void add() {
    std::unique_ptr<elem> temp(new elem());
    cout << "Enter an integer: ";
    cin >> temp->data;

    if (ls.head == nullptr) {//empty
        ls.head = std::move(temp);
    }
    else {
        if (ls.tail == nullptr) {  //1-item list
            ls.head->next = std::move(temp);
            ls.tail = ls.head->next.get();
            ls.tail->prev = ls.head.get();
        }
        else {
            temp->prev = ls.tail;
            ls.tail->next = std::move(temp);
            ls.tail = ls.tail->next.get();
        }
    }
}

void report() {
    if (ls.head == 0) cout << "List is empty!" << endl;
    else {
        elem *temp = ls.head.get();
        do {
            cout << temp->data << endl;
            temp = temp->next.get();
        } while (temp != 0);
    }
}

int main() {
    report();
    add();
    add();
    add();
    report();
    add();
    return 0;
}

The ownership of elements is now explicit, the list owns head and head owns its next node which owns its next node etc. Destroying the list automatically destroys the first node which automatically destroys the second node etc. In this code you can actually omit the destructors completely. This should also help to prevent memory leaks, for example if you decide to add some error checking to add the unused temp element gets automatically deleted:

void add() {
    std::unique_ptr<elem> temp(new elem());
    cout << "Enter an integer: ";
    cin >> temp->data;
    if (!cin || temp->data > 100) {
       cout << "invalid input value\n";
       return; // temp is automatically deleted here
    }
    ...
}
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60