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.