-1

I wrote a template class for Singly linked list. For printing values in reverse order, I implemented traverse_reverse() function using recursion. When the number of elements in list reaches near 4000, calling this function threw stack overflow error. At such range of numbers, I am not sure if stack overflow should happen.

Environment is Visual Studio 2019 Community edition, Windows 10 64 bit OS.

My questions are:

  1. How can I avoid stack overflow

  2. How can I increase the size of stack at runtime.

Below is the code snippet:


#pragma once
#include <mutex>
#include <iostream>

namespace MyDS
{
    template <typename T>
    struct Node
    {
        T* m_pData = nullptr;
        Node* m_pNext = nullptr;
    };

    template <class T>
    class sList
    {
        Node<T>* m_pHead = nullptr;
        Node<T>* m_pCurrentNode = nullptr;
        int m_Size = 0;
        std::mutex m_ListMutex;

    public:
        bool insert_front(T val);
        bool insert_last(T val);
        bool insert_anywhere(T val, int loc);

        bool remove(T val);
        //bool remove(int loc);
        bool remove_front();
        bool remove_last();

        void traverse();
        void traverse_reverse();

        bool emptyList();

        int getSize();

    private:
        void traverse_reverse(Node<T>* pNode);
    };

    template<typename T>
    void sList<T>::traverse_reverse(Node<T>* pNode)
    {
        if (pNode->m_pNext != nullptr)
            traverse_reverse(pNode->m_pNext);
        std::cout << *pNode->m_pData << " ";
    }

    template<typename T>
    bool sList<T>::emptyList()
    {
        bool ret = false;
        if (getSize() > 0)
        {
            std::lock_guard<std::mutex> lg(m_ListMutex);

            Node<T>* pTempNode = m_pHead, pTempNode1 = nullptr;
            while (pTempNode->m_pNext!= nullptr)
            {
                pTempNode1 = pTempNode->m_pNext;

                delete pTempNode->m_pData;
                delete pTempNode;
                pTempNode = pTempNode1;
            }

            delete pTempNode->m_pData;
            delete pTempNode;

            pTempNode->m_pData = pTempNode1->m_pData = m_pHead->m_pData = m_pCurrentNode->m_pData = nullptr;
            pTempNode = pTempNode1 = m_pHead = m_pCurrentNode = nullptr;
            m_Size = 0;
        }

        ret = true;
        return ret;
    }

    template<typename T>
    int sList<T>::getSize()
    {
        return m_Size;
    }

    template<typename T>
    bool sList<T>::insert_front(T val)
    {
        Node<T>* pNode = new Node<T>;
        pNode->m_pData = new T(val);

        if (getSize() > 0)
        {
            pNode->m_pNext = m_pHead;
        }

        m_pHead = pNode;
        m_Size++;

        return true;
    }


    template<typename T>
    bool sList<T>::insert_last(T val)
    {
        Node<T>* plastNode = m_pHead;
        while (plastNode->m_pNext!= nullptr)
            plastNode = plastNode->m_pNext;

        plastNode->m_pNext = new Node<T>;
        plastNode->m_pNext->m_pData = new T(val);

        return true;
    }


    template<typename T>
    bool sList<T>::insert_anywhere(T val, int loc)
    {
        return true;
    }


    //template<typename T>
    //bool sList<T>::remove(int loc)
    //{
    //  return true;
    //}


    template<typename T>
    bool sList<T>::remove_front()
    {
        std::lock_guard<std::mutex> lg(m_ListMutex);
        Node<T>* pNode = m_pHead;
        m_pHead = m_pHead->m_pNext;

        delete pNode->m_pData;
        delete pNode;

        m_Size--;
        return true;
    }

    template<typename T>
    bool sList<T>::remove_last()
    {
        Node<T>* plastNode = m_pHead;
        std::lock_guard<std::mutex> lg(m_ListMutex);
        if (getSize() > 1)
        {
            while (plastNode->m_pNext->m_pNext != nullptr)
                plastNode = plastNode->m_pNext;

            Node<T>* pNode = plastNode->m_pNext;
            plastNode->m_pNext = nullptr;

            delete pNode->m_pData;
            delete pNode;
            pNode->m_pData = pNode = nullptr;
            m_Size--;
        }
        else if(getSize() == 1) // Only 1 node 
        {
            delete m_pHead->m_pData;
            delete m_pHead;

            m_pHead->m_pData = m_pHead = nullptr;
            m_Size--;
        }
        else   // No node available
        {
            //Nothing to do 
        }

        return true;

    }

    template<typename T>
    bool sList<T>::remove(T val)
    {
        bool ret = false;
        Node<T>* pNode = m_pHead;
        Node<T>* pNodeNext = pNode->m_pNext;

        if (pNode->m_pData == val)
        {
            ret = remove_front();
        }
        else if (pNodeNext->m_pData == val)
        {
            pNode->m_pNext = pNodeNext->m_pNext;
            pNodeNext->m_pNext = nullptr;

            delete pNodeNext->m_pData;
            delete pNodeNext;
            pNodeNext->m_pData = pNodeNext = nullptr;
            ret = true;
            m_Size--;
        }
        else
        {
            while (pNodeNext->m_pData != val)
            {
                pNode = pNodeNext;
                pNodeNext = pNodeNext->m_pNext;
            }

            if (pNodeNext == nullptr)
                ret = false;
            else
            {
                pNode->m_pNext = pNodeNext->m_pNext;
                pNodeNext->m_pNext = nullptr;

                delete pNodeNext->m_pData;
                delete pNodeNext;
                pNodeNext->m_pData = pNodeNext = nullptr;
                m_Size--;
                ret = true;
            }
        }

        return ret;
    }

    template<typename T>
    void sList<T>::traverse()
    {
        m_pCurrentNode = m_pHead;
        while (m_pCurrentNode->m_pNext != nullptr)
        {
            std::cout << *m_pCurrentNode->m_pData<<" ";
            m_pCurrentNode = m_pCurrentNode->m_pNext;
        }

        std::cout << *m_pCurrentNode->m_pData;
        std::cout << std::endl;
    }

    template<typename T>
    void sList<T>::traverse_reverse()
    {
        m_pCurrentNode = m_pHead;
        traverse_reverse(m_pCurrentNode);
        std::cout << std::endl;
    }

}

#include "MyDS.h"

int main()
{
    MyDS::sList<int> myList;
    for(int i = 0; i <= 3987; ++i)
        myList.insert_front(i);

    myList.traverse_reverse(); //Recursion

//  myList.traverse();
    return 0;
}
user3505805
  • 147
  • 1
  • 9
  • [this](https://stackoverflow.com/questions/2630054/does-c-limit-recursion-depth) might be helpful. What does your implementation of `traverse_reverse(void)` look like? Traverse is usually what you do on a tree rather than a linked list, so this is a little unusual. – ChrisD Jun 04 '20 at 17:48
  • @ChrisD You can find it at the bottom of the first code block. user3505805 : In general, it is best to avoid having lots and lots of function calls on the stack. A single-linked list might be difficult reg. this. One option might be to reverse the single-linked list (maybe creating a new single-linked list), and then just traversing that one. A double-linked list would be able to do it very easily and efficiently. – MelvinWM Jun 04 '20 at 17:53
  • You cannot increase the size of the stack at *run* time, but you *can* do so at compile time with most compilers. It likely isn't what you really want though. – Jesper Juhl Jun 04 '20 at 18:04
  • 2
    Why are you using `mutexes` when the linked list is not working correctly, or at least has basic issues in a single-thread environment. Also, you should be posting code that represents a [mcve]. We don't know if the linked list is put together correctly or has issues, and you're not realizing it. – PaulMcKenzie Jun 04 '20 at 18:04
  • 1
    I don't see it at the bottom of the block. I see an implementation of `traverse_reverse(Node* pNode)`. – ChrisD Jun 04 '20 at 18:42
  • @ChrisD Good point, I apologize, I completely overlooked that there are two overloads of it. – MelvinWM Jun 04 '20 at 19:35
  • 1
    Also, on Windows, depending on your compiler options, your stack space can be a small as 512k (1M by default -- I think) where with gcc/clang you have a 4M stack by default. Creating 4000 copies of an overloaded templated function may well exhaust the stack space. – David C. Rankin Jun 04 '20 at 19:50
  • 1
    @PaulMcKenzie: Sorry for that if it is looking incomplete. I will update and add remaining code also. – user3505805 Jun 05 '20 at 03:25
  • 1
    @PaulMcKenzie: The list is working fine. The issue is only happening with the number of elements. I have compiled the code without mutex and the same issue is occurring. – user3505805 Jun 05 '20 at 03:40
  • @DavidC.Rankin: I will go through links shared here for increasing the stack size. – user3505805 Jun 05 '20 at 03:43
  • @user3505805 -- Increasing the stack size is a band-aid solution. You only had 4000 nodes, and the stack was exhausted. Just 4000 nodes is a tiny amount. Then when your new stack size can't accommodate 7 or 8 thousand nodes, then what? Keep creating a new program with a different stack amount? You might as well write am iterative solution using a while-loop, simulating a stack using a `std::stack` or similar data structure. Recursion has its limits. – PaulMcKenzie Jun 05 '20 at 04:17

3 Answers3

2

As the other answers have pointed out, you have not provided the full code. That said, guessing from the code you have given, I believe that the issue is that you are right about the stack overflow occurring due to too many function calls on the stack when the list of elements is sufficiently long.

In general, it is best to avoid having lots and lots of function calls on the stack. Increasing the stack size is most often not a good solution. See for instance why is stack memory size so limited? for some discussions on this topic.

A single-linked list might be difficult reg. this. One option might be to reverse the single-linked list (maybe creating a new single-linked list), and then just traversing that one (possibly deleting the created list afterwards). A double-linked list would be able to do it very easily and efficiently, since you can just find the last element and then go backwards from there.

MelvinWM
  • 749
  • 4
  • 13
  • 1
    I have updated the code snippet wit full implementation. I have gone through multiple implementation for traversing a singly linked list in reverse order. This includes using some extra containers and then displaying the content. You are right that for traversing in reverse order, it's better to use doubly linked list. – user3505805 Jun 05 '20 at 03:51
1

If you want to avoid stack overflow, don't use recursion. a simple while can do same job without requiring further resources from stack:

template<typename T>
void sList<T>::traverse_reverse(Node<T>* pNode)
{
    while (pNode != nullptr){
        std::cout << *pNode->m_pData << " ";
        pNode=pNode->m_pNext;
    }
}  

To increase stack size: Increase stack size in c++

However, in case the above code does not work, I suspect your problem is elsewhere. my initial guess is that you have an infinite loop (in recursion). Let's say for some reason, you have circular dependencies in your list: every node has his m_pNext filled with something other then nullptr. Recursion will never end, hence stackoverflow error. The code above will not work.

Usually circular dependencies arise from incorrect implementation of insert or remove methods. If for some reason you update your pointer in removal incorrectly to another node, it can cause circular dependency.

You can use following code to check for circular dependencies:

template<typename T>
void sList<T>::traverse_reverse(Node<T>* pNode)
{
    Node<T>* org_p=pNode;
    while (pNode->m_pNext != nullptr){
        pNode=pNode->m_pNext;
        if(org_p==pNode){
            std::cout << "Circular Dependency";
            break;            
        }
    }
    std::cout << "No Circular Dependency";
}  
Roim
  • 2,986
  • 2
  • 10
  • 25
  • 1
    I don't believe it is the case that it is an infinite loop. It sounds like a genuine stack overflow due to having too many function calls on the stack where an increased stack relative to the input size would work (though increasing the stack like that is generally the wrong approach). As far as I understand his question, he describes it working for inputs of single-linked lists with much smaller number of elements than 4000. – MelvinWM Jun 04 '20 at 18:01
  • @MelvinWM it something that should be examined. I ran into too many mistakes of edge cases (for example, removing nodes when next few nodes has same data value). Works on small inputs, but immediately changing scale usually surface some edge cases in insert/removal he did not test before. We can both agree that with this code we can only guess. I only try to suggest practical test to rule out circular dependency. – Roim Jun 04 '20 at 18:19
  • I don't think we need to guess, he included the implementation of the `traverse_reverse` function at the bottom of the first code-block. Though, I wonder whether the answer should be edited such that it is clearer that the implementation is actually available. – MelvinWM Jun 04 '20 at 18:28
  • 1
    @MelvinWM There are two functions name `traverse_reverse`, he only gave one implementation. That's why I keep my suggestion that problem may be in other place – Roim Jun 04 '20 at 18:41
  • You are completely right reg. the two overloads of `traverse_reverse`, I apologize. Reg. your new implementation, that would not be correct as far as I can tell, since it would print the list in the normal order, not the reverse order. – MelvinWM Jun 04 '20 at 19:36
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/215333/discussion-between-roim-and-melvinwm). – Roim Jun 04 '20 at 19:43
  • So you'd recommend heap overflow instead? The algorithm is the issue. – 3Dave Jun 05 '20 at 03:45
  • 1
    I have updated the code with full implementation. As stated by @MelvinWM, my main concern is to make it working on a big chunks of data. Getting overflow on 4000 elements is raising concern for me. Though the function traverse() is able to run successfully over a big chunk of data whereas traverse_reverse() is failing due to multiple recursion. – user3505805 Jun 05 '20 at 03:49
  • @user3505805 your code still not complete. There is one implementation of `traverse_reverse`, one with not arguments, although in recursion you call to this function with one argument. Also, `traverse` and `traverse_reverse` doing same action: printing all nodes from start to end. Where is the "reverse" part? – Roim Jun 05 '20 at 06:43
1

For printing values in reverse order, I implemented traverse_reverse() function using recursion.

Recursion (unless optimized by your compiler as tail-recursive calls) always consume call stack space. See also this draft report for examples of interesting GCC optimizations. Probably, your C++ compiler is capable of doing similar optimizations.

You could instead prefer consuming heap space, e.g. use intermediate standard C++ containers to keep temporary data.

You could be interested by continuation-passing style. Sometimes it enables you to avoid recursion (and use more heap memory instead).

You can find C++ implementations (recent GCC or Clang comes to mind...) whose source code of std::vector or std::list is open source and readable. You might be surprised by their complexity, related to the rule of five.

If you compiled your C++ code with a recent GCC, you could have used g++ -Wall -Wextra -g -fstack-protector -Wstack-usage=2048 perhaps combined with -O2 to be warned of large call frames.

You might be interested in static source program analysis tools such as Frama-C++, Coverity, Clang static analyzer, or the address sanitizer, or in writing your own GCC plugin to build a call graph and sometimes detect potential stack overflows (but be aware of Rice's theorem). See also valgrind.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547