Home > Back-end >  C undefined behavior with too many heap pointer deletions
C undefined behavior with too many heap pointer deletions

Time:11-04

I wrote a program to create a linked list, and I got undefined behavior (or I assume I did, given the program just stopped without any error) when I increased the size of the list to a certain degree and, critically, attempted to delete it (through ending its scope). A basic version of the code is below:

#include <iostream>
#include <memory>

template<typename T> struct Nodeptr;

template<class T>
struct Node {
    Nodeptr<T> next;
    T data;

    Node(const T& data) : data(data) { } 
};

template<class T>
struct Nodeptr : public std::shared_ptr<Node<T>> {
    Nodeptr() : std::shared_ptr<Node<T>>(nullptr) { }
    Nodeptr(const T& data) : std::shared_ptr<Node<T>>(new Node<T>(data)) { }
};

template<class T>
struct LinkedList {

    Nodeptr<T> head;

    void prepend(const T& data) {
        auto new_head = Nodeptr<T>(data);
        new_head->next = head;
        head = new_head;
    }
};

int main() {

    int iterations = 10000;

    {
        LinkedList<float> ls;
        std::cout << "START\n";
        for(float k = 0.0f; k < iterations; k  ) {
            ls.prepend(k);
        }
        std::cout << "COMPLETE\n";
    }

    std::cout << "DONE\n";

    return 0;
}

Right now, when the code is run, START and COMPLETE are printed, while DONE is not. The program exits prior without an error (for some reason). When I decrease the variable to, say, 5000 instead of 10000, it works just fine and DONE is printed. When I delete the curly braces around the LinkedList declaration/testing block (taking it out its smaller scope, causing it NOT to be deleted before DONE is printed), then everything works fine and DONE is printed. Therefore, the error must be arising because of the deletion process, and specifically because of the volume of things being deleted. There is, however, no error message telling me that there is no more space left in the heap, and 10000 floats seems like awfully little to be filling up the heap anyhow. Any help would be appreciated!


Solved! It now works directly off of heap pointers, and I changed Node's destructor to prevent recursive calls to it:

        ~Node() {
            if(!next) return;
            Node* ptr = next;
            Node* temp = nullptr;
            while(ptr) {
                temp = ptr->next;
                ptr->next = nullptr;
                delete ptr;
                ptr = temp;
            }
        }

CodePudding user response:

It is a stack overflow caused by recursive destructor calls.

This is a common issue with smart pointers one should be aware of when writing any deeply-nested data structure.

You need to an explicit destructor for Node removing elements iteratively by reseting the smart pointers starting from the tail of the list. Also follow the rule-of-3/5 and do the same for all other operations that might destroy nodes recursively as well.

Because this is essentially rewriting all object destruction it does however make use of smart pointers in the first place somewhat questionable, although there is still some benefit in preventing double delete (and leak) mistakes. Therefore it is common to simply not use smart pointers in such a situation at all and instead fall back to raw pointers and manual lifetime management for the data structure's nodes.

Also, there is no need to use std::shared_ptr here in any case. There is only one owner per node. It should be std::unique_ptr. std::shared_ptr has a very significant performance impact and also has the issue of potentially causing leaks if circular references are formed (whether intentionally or not).

I also don't see any point in having Nodeptr as a separate class. It seems to be used to just be an alias for std::make_shared<Node>, which can be achieved by just using a function instead. Especially inheriting from a standard library smart pointer seems dubious. If at all I would use composition instead.

  • Related