Juega con la lista enlazada de C

La lista enlazada es una estructura de datos de uso común en la programación en lenguaje C. Por ejemplo, si queremos construir una lista enlazada de enteros, generalmente se puede definir de la siguiente manera:

struct int_node {

        int val;

        struct int_node *next;

};

 

Para implementar funciones como inserción, eliminación y recorrido de la lista enlazada, se deben implementar una serie de funciones, tales como:

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

        }

}

 

Si solo hay una estructura de datos de este tipo en nuestro código, por supuesto que no hay problema en hacerlo, pero cuando la escala del código es lo suficientemente grande como para administrar muchos tipos de listas vinculadas, ¿necesitamos implementar un conjunto de inserción, eliminación, recorrido, etc. para cada lista enlazada ¿función función?

Los estudiantes que están familiarizados con C ++ pueden decir que podemos usar la biblioteca de plantillas estándar, pero aquí estamos hablando de C. ¿Hay una mejor manera en lenguaje C?

En este artículo, dirigimos nuestra atención al proyecto C más grande en el mundo de código abierto hoy en día: Linux Kernel , para ver cómo el kernel de Linux resuelve este problema.

En el kernel de Linux, generalmente se usa una lista doblemente enlazada, declarada como struct list_head.Esta estructura se define en include/linux/types.h, y se accede a la lista enlazada en forma de una macro o una función en línea en include/ definición de linux/list.h.

struct list_head {

    struct list_head *next, *prev;

};

 

El kernel de Linux proporciona una interfaz de acceso consistente para listas enlazadas.

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);

 

Las anteriores son solo algunas de las interfaces de uso común seleccionadas del kernel de Linux. Para obtener más definiciones, consulte el código fuente del kernel de Linux .

Primero establezcamos una comprensión perceptiva de cómo el kernel de Linux maneja las listas enlazadas a través de una implementación simple.

#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;

}

 

Después de leer esta implementación, ¿crees que es fácil administrar una lista enlazada en código C?

El archivo de encabezado list.h incluido en el código es el código de procesamiento de la lista enlazada que extraje del kernel de Linux y modifiqué un poco. Ahora lo adjunto aquí para su referencia. Cuando lo use, solo incluya este archivo de encabezado en su propio proyecto .Puede.

el código

list_head generalmente se usa incrustado en la estructura de datos. En la implementación anterior, todavía tomamos la lista enlazada de enteros como ejemplo. La definición de int_node es la siguiente:

struct int_node {

        int val;

        struct list_head list;

};

1

2

3

4

La estructura de la lista enlazada organizada por list_head se muestra en la siguiente figura:

El recorrido de la lista enlazada se realiza con la macro list_for_each.

#define list_for_each(pos, head) \

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

            pos = pos->next)

 

Aquí, tanto pos como head son estructuras list_head. Si necesita visitar un nodo durante el recorrido, puede usar list_entry para obtener la dirección base de este nodo.

#define list_entry(ptr, type, member) \

    container_of(ptr, type, member)

 

Echemos un vistazo a cómo se implementa container_of. Como se muestra en la figura siguiente, ya conocemos la dirección de MEMBER en la estructura TYPE.Si queremos obtener la dirección de esta estructura, solo necesitamos saber el desplazamiento de MEMBER en la estructura. ¿Cómo obtener esta dirección compensada? Aquí se usa un pequeño truco del lenguaje C. También podríamos proyectar la estructura al lugar donde la dirección es 0, entonces la dirección absoluta del miembro es el desplazamiento. Después de obtener el desplazamiento, la dirección de la estructura se puede calcular fácilmente según la dirección señalada por el puntero ptr.

list_entry es obtener la estructura de tipo que necesitamos del puntero ptr a través del método anterior.

El código del kernel de Linux es extenso y profundo, y el Sr. Chen Lijun lo describió una vez como "rodeado por más de trescientas millas, aislado del sol" (de " A Fang Gong Fu "), lo que muestra su rico contenido y estructura compleja. . Hay muchas estructuras de datos importantes en el kernel, y muchas de las estructuras de datos relacionadas están organizadas juntas por la lista enlazada presentada en este artículo Parece que aunque la estructura list_head es pequeña, es realmente útil.

El kernel de Linux es un gran proyecto, y todavía hay muchas sutilezas en su código fuente, que vale la pena leer detenidamente por parte de los programadores de C/C++ Incluso si no hacemos trabajo relacionado con el kernel, leer códigos maravillosos también mejorará la experiencia de los programadores. autocultivo Útil.

Supongo que te gusta

Origin blog.csdn.net/weixin_45925028/article/details/132236580
Recomendado
Clasificación