A Generic Design of linked list in C
Linked list is a very important data structure. In C++
, with the support of templating, we can implement a template linked likst class and use it for any data type we want. However, in C
, there is no such thing like templating. How can we implement a generic linked list? It is tedious to re-implement functions like list_add
and list_remove
for every data type that needs to be linked. Besides, it is hard to make one node in two lists simultaneously.
A traditional linked list node is something like this:
1 | typedef sturct NodeA nodea_t; |
With this design, one needs to implement a set of functions for every type of traditional node. This type of node is like a link-able node with extra data fields.
The design of generic linked list takes the pointers (linkable part) out of the data node. It enables more possible uses. The design is:
1 | typedef struct ListNode list_node_t; |
This design defines a linkable type list_node_t
and a list type list_t
, and put one or more members of list_node_t
type in the actual data structure that needs to be linked. We only need to implement functions for list_t
and list_node_t
, then we are done.
A possible implementation of list_push_back
and list_pop_back
could be like this:
1 | void list_push_back(list_t *l, list_node_t *node) { |
To use list_push_back
and list_pop_back
for nodeb_t
, we will use some macros:
1 | // uintptr_t is a type for address. |
__member_offset
is a macro that calculates the offset of member
in struct_type
. For example, the offset of tag_in_list_a
in nodeb_t
is 0
and that of tag_in_list_b
is sizeof(list_node_t)
.
__container_of
is to find the pointer to struct_type
by a pointer node_ptr
to member
.
With these two macros, we can define the following macros used for specific data types:
1 |
|
Sample usage:
1 | list_t *list_a; |
As we can see, this generic design is easy to use and solve the problem for push one node into different lists simultaneously.
This design is from the Linux
kernel.