Play with C linked list

Linked list is a commonly used data structure in C language programming. For example, if we want to build a linked list of integers, it may generally be defined as follows:

struct int_node {

        int val;

        struct int_node *next;

};

 

In order to implement functions such as insertion, deletion, and traversal of the linked list, a series of functions must be implemented, such as:

void insert_node(struct int_node **head, int val);



void delete_node(struct int_node *head, struct int_node *current);



void access_node(struct int_node *head)

{

        struct int_node *node;

        for (node = head; node != NULL; node = node->next) {

                // do something here

        }

}

 

If there is only such a data structure in our code, it is of course no problem to do so, but when the scale of the code is large enough to manage many kinds of linked lists, do we need to implement a set of insertion, deletion, traversal, etc. for each linked list? function function?

Students who are familiar with C++ may say that we can use the standard template library, but we are talking about C here. Is there a better way in C language?

In this article, we turn our attention to the largest C project in the open source world today - Linux Kernel , to see how the Linux kernel solves this problem.

In the Linux kernel, a doubly linked list is generally used, declared as struct list_head. This structure is defined in include/linux/types.h, and the linked list is accessed in the form of a macro or an inline function in include/linux/list.h definition.

struct list_head {

    struct list_head *next, *prev;

};

 

The Linux kernel provides a consistent access interface for linked lists.

void INIT_LIST_HEAD(struct list_head *list);

void list_add(struct list_head *new, struct list_head *head);

void list_add_tail(struct list_head *new, struct list_head *head);

void list_del(struct list_head *entry);

int list_empty(const struct list_head *head);

 

The above are just a few commonly used interfaces selected from the Linux kernel. For more definitions, please refer to the Linux kernel source code .

Let's first establish a perceptual understanding of how the Linux kernel handles linked lists through a simple implementation.

#include <stdio.h>

#include "list.h"



struct int_node {

        int val;

        struct list_head list;

};



int main()

{

        struct list_head head, *plist;

        struct int_node a, b;



        a.val = 2;

        b.val = 3;



        INIT_LIST_HEAD(&head);

        list_add(&a.list, &head);

        list_add(&b.list, &head);



        list_for_each(plist, &head) {

                struct int_node *node = list_entry(plist, struct int_node, list);

                printf("val = %d\n", node->val);

        }



        return 0;

}

 

After reading this implementation, do you think it is easy to manage a linked list in C code?

The header file list.h included in the code is the linked list processing code that I extracted from the Linux kernel and modified a little. Now I attach it here for your reference. When using it, just include this header file in your own project. Can.

the code

list_head is usually used embedded in the data structure. In the above implementation, we still take the integer linked list as an example. The definition of int_node is as follows:

struct int_node {

        int val;

        struct list_head list;

};

1

2

3

4

The structure of the linked list organized by list_head is shown in the following figure:

Traversing the linked list is done with the macro list_for_each.

#define list_for_each(pos, head) \

    for (pos = (head)->next; prefetch(pos->next), pos != (head); \

            pos = pos->next)

 

Here, both pos and head are struct list_head. If you need to visit a node during traversal, you can use list_entry to get the base address of this node.

#define list_entry(ptr, type, member) \

    container_of(ptr, type, member)

 

Let's take a look at how container_of is implemented. As shown in the figure below, we already know the address of MEMBER in the TYPE structure. If we want to get the address of this structure, we only need to know the offset of MEMBER in the structure. How to get this offset address? A little trick of the C language is used here. We might as well project the structure to the place where the address is 0, then the absolute address of the member is the offset. After obtaining the offset, the address of the structure can be easily calculated according to the address pointed to by the ptr pointer.

list_entry is to get the type structure we need from the ptr pointer through the above method.

The Linux kernel code is extensive and profound, and Mr. Chen Lijun once described it as "surrounded by more than three hundred miles, isolated from the sun" (from " A Fang Gong Fu "), which shows its rich content and complex structure. There are many important data structures in the kernel, and many of the related data structures are organized together by the linked list introduced in this article. It seems that although the list_head structure is small, it is really useful.

The Linux kernel is a great project, and there are still many subtleties in its source code, which are worth reading carefully by C/C++ programmers. Even if we don't do kernel-related work, reading wonderful codes will also improve programmers' self-cultivation. Useful.

Guess you like

Origin blog.csdn.net/weixin_45925028/article/details/132236580