Scope of heap memory allocated with new in c++












0















New learner for data structure, recently learning Listnode. Got questions.
Define a ListNode:



struct ListNode
{
int val;
ListNode* next;
ListNode(int x) : val(x), next(NULL) {}
};
void AddToTail(ListNode** head, int value)
{
ListNode* nlist = new ListNode(value);
if(*head == NULL)
{
*head = nlist;
}
else
{
ListNode* p = *head;
while(p->next != NULL)
{
p = p->next;
}
p->next = nlist;
}
// No need to delete nlist, since the memory allocated by it is already organized by the ListNode.
}

int main()
{
ListNode* L = new ListNode(5);
AddToTail(&L, 10);
ListNode* p = L;
while(p->next != NULL)
{
p = p->next;
}
printf("Final value is %d.n", p->val);
delete L;
}


Till now, everything seems ok right? I wonder what if i don't allocate the ListNode memory in heap, that means i don't use new, while i use ListNode L; then initialize it manually. The memeory of it is now in stack. The tail added to it is in heap memory. How do i delete the heap memory right now. It seems part of the ListNode memory is in stack and part of it is in heap? Am i correctly understood?










share|improve this question























  • You're leaking memory regardless. Nothing is deleting the node created by AddToTail.

    – Rotem
    Nov 15 '18 at 10:30






  • 1





    Don't mix memory allocated on the stack and in the heap in a container without tracking who is responsible for deleting the objects.

    – Matthieu Brucher
    Nov 15 '18 at 10:30











  • @Rotem Then how can i delete the tail added by AddToTail?

    – BigEd
    Nov 15 '18 at 10:36











  • ALL dynamically allocated objects (e.g. created by a new expression) must be explicitly released (e.g. with a corresponding delete expression) and no other objects should be explicitly released. That is true regardless of how you mix up objects "in stack" (incorrect description for an object of automatic storage duration) with "in heap" (incorrect description of pointer to dynamically allocated object). If you mix use of dynamic memory allocation and objects of automatic storage duration, you are responsible for ensuring only the dynamically allocated ones are released.

    – Peter
    Nov 15 '18 at 10:38











  • @BigEd Depends how you define responsibility in your data structure. Something must be in charge of calling delete on newed objects. One scenario might be that each node deletes the subsequent node before deleting itself, triggering a recursive deletion. Another scenario is that you delegate that responsibility to the user of the data structure, and if you do, it might make more sense to also delegate the responsibility of allocating the ListNode to the user as well.

    – Rotem
    Nov 15 '18 at 10:48


















0















New learner for data structure, recently learning Listnode. Got questions.
Define a ListNode:



struct ListNode
{
int val;
ListNode* next;
ListNode(int x) : val(x), next(NULL) {}
};
void AddToTail(ListNode** head, int value)
{
ListNode* nlist = new ListNode(value);
if(*head == NULL)
{
*head = nlist;
}
else
{
ListNode* p = *head;
while(p->next != NULL)
{
p = p->next;
}
p->next = nlist;
}
// No need to delete nlist, since the memory allocated by it is already organized by the ListNode.
}

int main()
{
ListNode* L = new ListNode(5);
AddToTail(&L, 10);
ListNode* p = L;
while(p->next != NULL)
{
p = p->next;
}
printf("Final value is %d.n", p->val);
delete L;
}


Till now, everything seems ok right? I wonder what if i don't allocate the ListNode memory in heap, that means i don't use new, while i use ListNode L; then initialize it manually. The memeory of it is now in stack. The tail added to it is in heap memory. How do i delete the heap memory right now. It seems part of the ListNode memory is in stack and part of it is in heap? Am i correctly understood?










share|improve this question























  • You're leaking memory regardless. Nothing is deleting the node created by AddToTail.

    – Rotem
    Nov 15 '18 at 10:30






  • 1





    Don't mix memory allocated on the stack and in the heap in a container without tracking who is responsible for deleting the objects.

    – Matthieu Brucher
    Nov 15 '18 at 10:30











  • @Rotem Then how can i delete the tail added by AddToTail?

    – BigEd
    Nov 15 '18 at 10:36











  • ALL dynamically allocated objects (e.g. created by a new expression) must be explicitly released (e.g. with a corresponding delete expression) and no other objects should be explicitly released. That is true regardless of how you mix up objects "in stack" (incorrect description for an object of automatic storage duration) with "in heap" (incorrect description of pointer to dynamically allocated object). If you mix use of dynamic memory allocation and objects of automatic storage duration, you are responsible for ensuring only the dynamically allocated ones are released.

    – Peter
    Nov 15 '18 at 10:38











  • @BigEd Depends how you define responsibility in your data structure. Something must be in charge of calling delete on newed objects. One scenario might be that each node deletes the subsequent node before deleting itself, triggering a recursive deletion. Another scenario is that you delegate that responsibility to the user of the data structure, and if you do, it might make more sense to also delegate the responsibility of allocating the ListNode to the user as well.

    – Rotem
    Nov 15 '18 at 10:48
















0












0








0








New learner for data structure, recently learning Listnode. Got questions.
Define a ListNode:



struct ListNode
{
int val;
ListNode* next;
ListNode(int x) : val(x), next(NULL) {}
};
void AddToTail(ListNode** head, int value)
{
ListNode* nlist = new ListNode(value);
if(*head == NULL)
{
*head = nlist;
}
else
{
ListNode* p = *head;
while(p->next != NULL)
{
p = p->next;
}
p->next = nlist;
}
// No need to delete nlist, since the memory allocated by it is already organized by the ListNode.
}

int main()
{
ListNode* L = new ListNode(5);
AddToTail(&L, 10);
ListNode* p = L;
while(p->next != NULL)
{
p = p->next;
}
printf("Final value is %d.n", p->val);
delete L;
}


Till now, everything seems ok right? I wonder what if i don't allocate the ListNode memory in heap, that means i don't use new, while i use ListNode L; then initialize it manually. The memeory of it is now in stack. The tail added to it is in heap memory. How do i delete the heap memory right now. It seems part of the ListNode memory is in stack and part of it is in heap? Am i correctly understood?










share|improve this question














New learner for data structure, recently learning Listnode. Got questions.
Define a ListNode:



struct ListNode
{
int val;
ListNode* next;
ListNode(int x) : val(x), next(NULL) {}
};
void AddToTail(ListNode** head, int value)
{
ListNode* nlist = new ListNode(value);
if(*head == NULL)
{
*head = nlist;
}
else
{
ListNode* p = *head;
while(p->next != NULL)
{
p = p->next;
}
p->next = nlist;
}
// No need to delete nlist, since the memory allocated by it is already organized by the ListNode.
}

int main()
{
ListNode* L = new ListNode(5);
AddToTail(&L, 10);
ListNode* p = L;
while(p->next != NULL)
{
p = p->next;
}
printf("Final value is %d.n", p->val);
delete L;
}


Till now, everything seems ok right? I wonder what if i don't allocate the ListNode memory in heap, that means i don't use new, while i use ListNode L; then initialize it manually. The memeory of it is now in stack. The tail added to it is in heap memory. How do i delete the heap memory right now. It seems part of the ListNode memory is in stack and part of it is in heap? Am i correctly understood?







c++






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 15 '18 at 10:24









BigEdBigEd

212




212













  • You're leaking memory regardless. Nothing is deleting the node created by AddToTail.

    – Rotem
    Nov 15 '18 at 10:30






  • 1





    Don't mix memory allocated on the stack and in the heap in a container without tracking who is responsible for deleting the objects.

    – Matthieu Brucher
    Nov 15 '18 at 10:30











  • @Rotem Then how can i delete the tail added by AddToTail?

    – BigEd
    Nov 15 '18 at 10:36











  • ALL dynamically allocated objects (e.g. created by a new expression) must be explicitly released (e.g. with a corresponding delete expression) and no other objects should be explicitly released. That is true regardless of how you mix up objects "in stack" (incorrect description for an object of automatic storage duration) with "in heap" (incorrect description of pointer to dynamically allocated object). If you mix use of dynamic memory allocation and objects of automatic storage duration, you are responsible for ensuring only the dynamically allocated ones are released.

    – Peter
    Nov 15 '18 at 10:38











  • @BigEd Depends how you define responsibility in your data structure. Something must be in charge of calling delete on newed objects. One scenario might be that each node deletes the subsequent node before deleting itself, triggering a recursive deletion. Another scenario is that you delegate that responsibility to the user of the data structure, and if you do, it might make more sense to also delegate the responsibility of allocating the ListNode to the user as well.

    – Rotem
    Nov 15 '18 at 10:48





















  • You're leaking memory regardless. Nothing is deleting the node created by AddToTail.

    – Rotem
    Nov 15 '18 at 10:30






  • 1





    Don't mix memory allocated on the stack and in the heap in a container without tracking who is responsible for deleting the objects.

    – Matthieu Brucher
    Nov 15 '18 at 10:30











  • @Rotem Then how can i delete the tail added by AddToTail?

    – BigEd
    Nov 15 '18 at 10:36











  • ALL dynamically allocated objects (e.g. created by a new expression) must be explicitly released (e.g. with a corresponding delete expression) and no other objects should be explicitly released. That is true regardless of how you mix up objects "in stack" (incorrect description for an object of automatic storage duration) with "in heap" (incorrect description of pointer to dynamically allocated object). If you mix use of dynamic memory allocation and objects of automatic storage duration, you are responsible for ensuring only the dynamically allocated ones are released.

    – Peter
    Nov 15 '18 at 10:38











  • @BigEd Depends how you define responsibility in your data structure. Something must be in charge of calling delete on newed objects. One scenario might be that each node deletes the subsequent node before deleting itself, triggering a recursive deletion. Another scenario is that you delegate that responsibility to the user of the data structure, and if you do, it might make more sense to also delegate the responsibility of allocating the ListNode to the user as well.

    – Rotem
    Nov 15 '18 at 10:48



















You're leaking memory regardless. Nothing is deleting the node created by AddToTail.

– Rotem
Nov 15 '18 at 10:30





You're leaking memory regardless. Nothing is deleting the node created by AddToTail.

– Rotem
Nov 15 '18 at 10:30




1




1





Don't mix memory allocated on the stack and in the heap in a container without tracking who is responsible for deleting the objects.

– Matthieu Brucher
Nov 15 '18 at 10:30





Don't mix memory allocated on the stack and in the heap in a container without tracking who is responsible for deleting the objects.

– Matthieu Brucher
Nov 15 '18 at 10:30













@Rotem Then how can i delete the tail added by AddToTail?

– BigEd
Nov 15 '18 at 10:36





@Rotem Then how can i delete the tail added by AddToTail?

– BigEd
Nov 15 '18 at 10:36













ALL dynamically allocated objects (e.g. created by a new expression) must be explicitly released (e.g. with a corresponding delete expression) and no other objects should be explicitly released. That is true regardless of how you mix up objects "in stack" (incorrect description for an object of automatic storage duration) with "in heap" (incorrect description of pointer to dynamically allocated object). If you mix use of dynamic memory allocation and objects of automatic storage duration, you are responsible for ensuring only the dynamically allocated ones are released.

– Peter
Nov 15 '18 at 10:38





ALL dynamically allocated objects (e.g. created by a new expression) must be explicitly released (e.g. with a corresponding delete expression) and no other objects should be explicitly released. That is true regardless of how you mix up objects "in stack" (incorrect description for an object of automatic storage duration) with "in heap" (incorrect description of pointer to dynamically allocated object). If you mix use of dynamic memory allocation and objects of automatic storage duration, you are responsible for ensuring only the dynamically allocated ones are released.

– Peter
Nov 15 '18 at 10:38













@BigEd Depends how you define responsibility in your data structure. Something must be in charge of calling delete on newed objects. One scenario might be that each node deletes the subsequent node before deleting itself, triggering a recursive deletion. Another scenario is that you delegate that responsibility to the user of the data structure, and if you do, it might make more sense to also delegate the responsibility of allocating the ListNode to the user as well.

– Rotem
Nov 15 '18 at 10:48







@BigEd Depends how you define responsibility in your data structure. Something must be in charge of calling delete on newed objects. One scenario might be that each node deletes the subsequent node before deleting itself, triggering a recursive deletion. Another scenario is that you delegate that responsibility to the user of the data structure, and if you do, it might make more sense to also delegate the responsibility of allocating the ListNode to the user as well.

– Rotem
Nov 15 '18 at 10:48














2 Answers
2






active

oldest

votes


















4














Yeah, it doesn't really matter whether L is dynamically allocated or not. That's by-the-by. In fact there's no reason here to dynamically allocate it.



What matters is the ownership and lifetime of the nodes that it manages. These are dynamically allocated, so you will need a destructor inside ListNode that unlinks and de-allocates the nodes.



There's nothing wrong with "mixing" storage duration — that's what you're doing every time you instantiate a std::vector<int>, for example. You just need to ensure that anything owning dynamically allocated data has the capability to clean up after itself.






share|improve this answer































    1














    You need some delete function like this



        void DeleteList(ListNode** head)
    {
    ListNode* p = *head;
    while(p != NULL)
    {
    ListNode *t = p->next;
    delete p;
    p = t;
    }
    }

    // and call it in main:

    printf("Final value is %d.n", p->val);
    DeleteList(&L);


    But it is better to immediately define all these operations in a special class, such as List:



    class List
    {
    private:
    ListNode *head;
    public:
    List() : head(NULL){}
    ~List() { Clear(); }
    void AddToTail(int value);
    void Clear();
    };

    void List::AddToTail(int value)
    {
    ListNode* nlist = new ListNode(value);
    if(head == NULL)
    {
    head = nlist;
    }
    else
    {
    ListNode* p = head;
    while(p->next != NULL)
    {
    p = p->next;
    }
    p->next = nlist;
    }
    }


    void List::Clear()
    {
    ListNode* p = head;
    while(p != NULL)
    {
    ListNode *t = p->next;
    delete p;
    p = t;
    }
    head = NULL;
    }


    and use it like this:



    List lst;
    lst.AddToTail(5);
    lst.AddToTail(10);
    lst.AddToTail(20);
    lst.AddToTail(30);
    lst.Clear();





    share|improve this answer

























      Your Answer






      StackExchange.ifUsing("editor", function () {
      StackExchange.using("externalEditor", function () {
      StackExchange.using("snippets", function () {
      StackExchange.snippets.init();
      });
      });
      }, "code-snippets");

      StackExchange.ready(function() {
      var channelOptions = {
      tags: "".split(" "),
      id: "1"
      };
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using("snippets", function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: true,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: 10,
      bindNavPrevention: true,
      postfix: "",
      imageUploader: {
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      },
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      });


      }
      });














      draft saved

      draft discarded


















      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53317266%2fscope-of-heap-memory-allocated-with-new-in-c%23new-answer', 'question_page');
      }
      );

      Post as a guest















      Required, but never shown

























      2 Answers
      2






      active

      oldest

      votes








      2 Answers
      2






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      4














      Yeah, it doesn't really matter whether L is dynamically allocated or not. That's by-the-by. In fact there's no reason here to dynamically allocate it.



      What matters is the ownership and lifetime of the nodes that it manages. These are dynamically allocated, so you will need a destructor inside ListNode that unlinks and de-allocates the nodes.



      There's nothing wrong with "mixing" storage duration — that's what you're doing every time you instantiate a std::vector<int>, for example. You just need to ensure that anything owning dynamically allocated data has the capability to clean up after itself.






      share|improve this answer




























        4














        Yeah, it doesn't really matter whether L is dynamically allocated or not. That's by-the-by. In fact there's no reason here to dynamically allocate it.



        What matters is the ownership and lifetime of the nodes that it manages. These are dynamically allocated, so you will need a destructor inside ListNode that unlinks and de-allocates the nodes.



        There's nothing wrong with "mixing" storage duration — that's what you're doing every time you instantiate a std::vector<int>, for example. You just need to ensure that anything owning dynamically allocated data has the capability to clean up after itself.






        share|improve this answer


























          4












          4








          4







          Yeah, it doesn't really matter whether L is dynamically allocated or not. That's by-the-by. In fact there's no reason here to dynamically allocate it.



          What matters is the ownership and lifetime of the nodes that it manages. These are dynamically allocated, so you will need a destructor inside ListNode that unlinks and de-allocates the nodes.



          There's nothing wrong with "mixing" storage duration — that's what you're doing every time you instantiate a std::vector<int>, for example. You just need to ensure that anything owning dynamically allocated data has the capability to clean up after itself.






          share|improve this answer













          Yeah, it doesn't really matter whether L is dynamically allocated or not. That's by-the-by. In fact there's no reason here to dynamically allocate it.



          What matters is the ownership and lifetime of the nodes that it manages. These are dynamically allocated, so you will need a destructor inside ListNode that unlinks and de-allocates the nodes.



          There's nothing wrong with "mixing" storage duration — that's what you're doing every time you instantiate a std::vector<int>, for example. You just need to ensure that anything owning dynamically allocated data has the capability to clean up after itself.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 15 '18 at 11:25









          Lightness Races in OrbitLightness Races in Orbit

          292k53475807




          292k53475807

























              1














              You need some delete function like this



                  void DeleteList(ListNode** head)
              {
              ListNode* p = *head;
              while(p != NULL)
              {
              ListNode *t = p->next;
              delete p;
              p = t;
              }
              }

              // and call it in main:

              printf("Final value is %d.n", p->val);
              DeleteList(&L);


              But it is better to immediately define all these operations in a special class, such as List:



              class List
              {
              private:
              ListNode *head;
              public:
              List() : head(NULL){}
              ~List() { Clear(); }
              void AddToTail(int value);
              void Clear();
              };

              void List::AddToTail(int value)
              {
              ListNode* nlist = new ListNode(value);
              if(head == NULL)
              {
              head = nlist;
              }
              else
              {
              ListNode* p = head;
              while(p->next != NULL)
              {
              p = p->next;
              }
              p->next = nlist;
              }
              }


              void List::Clear()
              {
              ListNode* p = head;
              while(p != NULL)
              {
              ListNode *t = p->next;
              delete p;
              p = t;
              }
              head = NULL;
              }


              and use it like this:



              List lst;
              lst.AddToTail(5);
              lst.AddToTail(10);
              lst.AddToTail(20);
              lst.AddToTail(30);
              lst.Clear();





              share|improve this answer






























                1














                You need some delete function like this



                    void DeleteList(ListNode** head)
                {
                ListNode* p = *head;
                while(p != NULL)
                {
                ListNode *t = p->next;
                delete p;
                p = t;
                }
                }

                // and call it in main:

                printf("Final value is %d.n", p->val);
                DeleteList(&L);


                But it is better to immediately define all these operations in a special class, such as List:



                class List
                {
                private:
                ListNode *head;
                public:
                List() : head(NULL){}
                ~List() { Clear(); }
                void AddToTail(int value);
                void Clear();
                };

                void List::AddToTail(int value)
                {
                ListNode* nlist = new ListNode(value);
                if(head == NULL)
                {
                head = nlist;
                }
                else
                {
                ListNode* p = head;
                while(p->next != NULL)
                {
                p = p->next;
                }
                p->next = nlist;
                }
                }


                void List::Clear()
                {
                ListNode* p = head;
                while(p != NULL)
                {
                ListNode *t = p->next;
                delete p;
                p = t;
                }
                head = NULL;
                }


                and use it like this:



                List lst;
                lst.AddToTail(5);
                lst.AddToTail(10);
                lst.AddToTail(20);
                lst.AddToTail(30);
                lst.Clear();





                share|improve this answer




























                  1












                  1








                  1







                  You need some delete function like this



                      void DeleteList(ListNode** head)
                  {
                  ListNode* p = *head;
                  while(p != NULL)
                  {
                  ListNode *t = p->next;
                  delete p;
                  p = t;
                  }
                  }

                  // and call it in main:

                  printf("Final value is %d.n", p->val);
                  DeleteList(&L);


                  But it is better to immediately define all these operations in a special class, such as List:



                  class List
                  {
                  private:
                  ListNode *head;
                  public:
                  List() : head(NULL){}
                  ~List() { Clear(); }
                  void AddToTail(int value);
                  void Clear();
                  };

                  void List::AddToTail(int value)
                  {
                  ListNode* nlist = new ListNode(value);
                  if(head == NULL)
                  {
                  head = nlist;
                  }
                  else
                  {
                  ListNode* p = head;
                  while(p->next != NULL)
                  {
                  p = p->next;
                  }
                  p->next = nlist;
                  }
                  }


                  void List::Clear()
                  {
                  ListNode* p = head;
                  while(p != NULL)
                  {
                  ListNode *t = p->next;
                  delete p;
                  p = t;
                  }
                  head = NULL;
                  }


                  and use it like this:



                  List lst;
                  lst.AddToTail(5);
                  lst.AddToTail(10);
                  lst.AddToTail(20);
                  lst.AddToTail(30);
                  lst.Clear();





                  share|improve this answer















                  You need some delete function like this



                      void DeleteList(ListNode** head)
                  {
                  ListNode* p = *head;
                  while(p != NULL)
                  {
                  ListNode *t = p->next;
                  delete p;
                  p = t;
                  }
                  }

                  // and call it in main:

                  printf("Final value is %d.n", p->val);
                  DeleteList(&L);


                  But it is better to immediately define all these operations in a special class, such as List:



                  class List
                  {
                  private:
                  ListNode *head;
                  public:
                  List() : head(NULL){}
                  ~List() { Clear(); }
                  void AddToTail(int value);
                  void Clear();
                  };

                  void List::AddToTail(int value)
                  {
                  ListNode* nlist = new ListNode(value);
                  if(head == NULL)
                  {
                  head = nlist;
                  }
                  else
                  {
                  ListNode* p = head;
                  while(p->next != NULL)
                  {
                  p = p->next;
                  }
                  p->next = nlist;
                  }
                  }


                  void List::Clear()
                  {
                  ListNode* p = head;
                  while(p != NULL)
                  {
                  ListNode *t = p->next;
                  delete p;
                  p = t;
                  }
                  head = NULL;
                  }


                  and use it like this:



                  List lst;
                  lst.AddToTail(5);
                  lst.AddToTail(10);
                  lst.AddToTail(20);
                  lst.AddToTail(30);
                  lst.Clear();






                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited Nov 15 '18 at 11:32

























                  answered Nov 15 '18 at 11:19









                  snake_stylesnake_style

                  1,170410




                  1,170410






























                      draft saved

                      draft discarded




















































                      Thanks for contributing an answer to Stack Overflow!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53317266%2fscope-of-heap-memory-allocated-with-new-in-c%23new-answer', 'question_page');
                      }
                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      Xamarin.iOS Cant Deploy on Iphone

                      Glorious Revolution

                      Dulmage-Mendelsohn matrix decomposition in Python