0

I was reviewing some libraries in C for a systems programming class, and I noticed their implementation of a doubly linked list did not require dynamically allocated data. Instead a list of a potentially dynamically allocated struct required an embedded list element, which with a list_entry macro would convert any list element back to the stored structure. For example given a struct foo:

struct list_elem
{
  struct list_elem *prev; 
  struct list_elem *next; 
};
struct list
{
  struct list_elem head; 
  struct list_elem tail; 
};
struct list foo_list;
list_init(&foo_list);
...

struct list_elem *e = list_begin(&foo_list); 
struct foo *f = list_entry(e, struct foo, sharedelem);  // Where sharedelem is of list_elem in struct foo

My question, is this advantageous and/or applicable outside of systems programming? Or would using an ambiguous type implementation of a linked list be preferable (wherein the linked list itself is malloc'ed and points to void elements).

  • 1
    This way of implementing a linked list is called an "intrusive linked list". It avoids extra indirection and separate allocation of the list node. But you still have to heap allocate the actual struct instance being added to the list. – Ajay Brahmakshatriya Mar 30 '22 at 23:55
  • the huge difference is that when the function creating the list on the stack returns the list is no longer valid since the stack frame its built on is gone – pm100 Mar 31 '22 at 00:20

1 Answers1

0

There are two cases:

  1. The program/library/service owns the data, and is not sharing it. In this case, altering the internal data structures to support different types of access/traversing (linked list, tree etc) can be advantageous, for speed and memory footprint reasons. However, this data should not leak to a client of the program/library/service, because the client code can mess with the internals (and eventually screwing it for all potential clients).

  2. The program/library/service handles client's data on behalf of the client, so it shares it with the client code. In this case, the structures for accessing/traversing the client's data must "wrap" around it, without altering client's data. This is because the client code assumes data persistency from containers, e.g. "I gave you an int, I expect an int with the same value back".

I hope that these two cases explain why both approaches have merits, depending on the business case.

user51187286016
  • 256
  • 1
  • 5