Dynamically allocated doubly linked circular list class instances segfault C++

时间:2015-05-12 23:07:09

标签: c++11 linked-list segmentation-fault dynamic-programming

Using this template class works perfectly fine when main operates with constructed variables of type dlring, yet my goal is to allow dynamic allocation, so I can handle a non-predefined number of doubly linked circular lists to allow usage of such functions as:

  • Splitting a list into two by either using a node position (via
    iteration) or value entry.
  • Same goes for linking two lists into one with a single head/tail pair.
  • Node exporting from one list (instance) to another.
  • Etc.

I'm pretty much sure there is an elegant workaround which is simply not known by me yet, but I don't think it's nice to come up with a question for the community if you didn't struggle enough to resolve. (checked google

So with that goals I'm supposed to dynamically allocate memory (via constructor calls) using some kind of pointer-to-pointer, AFAIK. If there is a smarter way to implement these, please let me know. My solution attempt is given in the end of this snippet. Feel free to criticize all of the below.

Doubly linked circular list class header (simplified)

Echo "Please enter name: "
read name 
read -r -p "Is this a costumer? (Y/N)" response;
if [[ $response =~ ^([yY][eE][sS]|[yY])$ ]]
then 
    echo "Please enter name: "
    read name
    AreYouDone
else
    "Please enter  name "
    read name2
    AreYouDone
fi

echo $name is a costumer  
echo $name2 is an employer

Should I use the commented out operator bool overload?

pop_back and Push methods:

template <typename T>
class dlring
{
    struct node
    {
        T data;
        node* prev;
        node* next;
        node(T t, node* p, node* n) : data(t), prev(p), next(n) {}
    };
    node* head;
    node* tail;
public:
    dlring():head(nullptr), tail(nullptr){}
    bool empty() const { return ( !head || !tail ); }
//operator bool() const { return !empty(); }
    void Push(T);
    T pop_back();
    ~dlring()
    {
        while(head)
        {
            node* temp(head);
            head=head->next;
            delete temp;
        }
    }
};

My attempt doesn't have the right behaviour: When I'm trying to show all the lists via a iteration the code fails, segfaulting on head->data access attempt of dlist[0], where 0 is an iteration of k. Here is the snippet:

template <typename T>
void dlring<T>::Push(T data)
{
    head = new node(data, tail, head); 
    if( head->next )
    {
        head->next->prev = head;
        tail->next = head;
    }
    if( empty() )
    {
        tail = head;
        head->next=tail;
        head->prev=tail;
        tail->next=head;
        tail->prev=head;
    }
}
template<typename T>
T dlring<T>::pop_back()
{
    if( empty() )
        std::cout<<"List empty";
    node* temp(tail);
    T data( tail->data );
    tail = tail->prev ;
    if (tail != temp)
    {
        tail->next->next = head; 
        head->prev = tail;
    }
    else
    {
        head = nullptr;
        tail = nullptr;
    }
    delete temp;
    temp = nullptr;
    return data;
}

Best regards. Again, feel free to criticize any of my code/logics.

1 个答案:

答案 0 :(得分:0)

  for(int i=0;i<k;i++)
  {
    while(!dlist[k].empty())
    std::cout<<(dlist[k]).pop_back()<<" ";
    std::cout<<std::endl;
  }

没有使用迭代器i。它应该是:

  for(int i=0;i<k;i++)
  {
    while(!dlist[i].empty())
    std::cout<<(dlist[i]).pop_back()<<" ";
    std::cout<<std::endl;
  }

由于dlist是一个大小为k的数组,原始代码会产生越界访问。

由于前面提到的循环使dlist数组中的每个列表都为空,因此所有列表中的head都是空指针。请注意,您根本无法访问它,因为它是私有成员。如果您这样做,则在解除引用时会出现段错误:

std::cout<<(dlist[0]).head->data;

如果在程序的这一点编译dlist[0].head == nullptr,那么就会出现段错误。

另请注意,由于您未发布动态分配的dlist,因此您会泄漏内存。附加到您的程序结束:

delete[] dlist;

通过这些更改,我不会从clang的地址清理工具中获得任何段错误,问题或报告。

另一个问题(在main中没有表现出来)是在tail中设置pop_back。我将尝试使用一些ASCII艺术作为插图。一个方框

   D ->
<- D

表示具有Data的节点,next指针和prev指针。 所有箭头都是指针。

非空列表:

   +-----------------------------+
   v                             |
   D ->    D -> ..    D ->    D -+
+- D    <- D    .. <- D    <- D
|                             ^
+-----------------------------+
   ^                          ^
   |head                      |tail

head->prev指向与tail相同的对象,类似地,tail->next指向与head相同的对象。

现在,一个&#34;动画&#34; pop_back函数。

template<typename T>
T dlring<T>::pop_back()
{
    if( empty() )
        std::cout<<"List empty";
    node* temp(tail);

    /*
       +-----------------------------+
       v                             |
       D ->    D -> ..    D ->    D -+
    +- D    <- D    .. <- D    <- D
    |                             ^
    +-----------------------------+
       ^                          ^
       |head                      |tail
                                  |temp
    */

    T data( tail->data );
    tail = tail->prev ;

    /*
       +-----------------------------+
       v                             |
       D ->    D -> ..    D ->    D -+
    +- D    <- D    .. <- D    <- D
    |                             ^
    +-----------------------------+
       ^                  ^       ^
       |head              |tail   |temp
    */

    if (tail != temp)
    {
        tail->next->next = head; 

        /*
           +-----------------------------+
           v                             |
           D ->    D -> ..    D ->    D -+  (A)
        +- D    <- D    .. <- D    <- D
        |                             ^
        +-----------------------------+
           ^                  ^       ^
           |head              |tail   |temp

        The pointer (A) is what is changed when writing to tail->next->next.
        Yes, nothing has actually changed in the list!
        */

        head->prev = tail;

        /*
           +-----------------------------+
           v                             |
           D ->    D -> ..    D ->    D -+
        +- D    <- D    .. <- D    <- D
        |                     ^
        +---------------------+
           ^                  ^       ^
           |head              |tail   |temp
        */

    }
    else
    {
        head = nullptr;
        tail = nullptr;
    }
    delete temp;

    /*
       D ->    D -> ..    D ->
    +- D    <- D    .. <- D
    |                     ^
    +---------------------+
       ^                  ^       ^
       |head              |tail   |temp
    */

    temp = nullptr;
    return data;
}

请注意,在最后一张&#34;图片&#34;中,tail->next是一个无效指针。

相反,它应该是:

    if (tail != temp)
    {
        tail->next = head; 

        /*
           +---------------------+-------+
           v                     |       |
           D ->    D -> ..    D -+    D -+
        +- D    <- D    .. <- D    <- D
        |                             ^
        +-----------------------------+
           ^                  ^       ^
           |head              |tail   |temp

删除temp后(没有进一步的更改),它将如下所示:

        /*
           +---------------------+
           v                     |
           D ->    D -> ..    D -+
        +- D    <- D    .. <- D
        |                     ^
        +---------------------+
           ^                  ^       ^
           |head              |tail   |temp

最后但同样重要的是,请注意您违反了Rule of Three