Linear table of data structure (bsd, sys/queue.h)

Linear table of data structure

Author: Once Day Date: May 27, 2023

Reference documents:

1 Overview

  • A linear list (linear list) has unique head data and tail data, and each data in the middle has a predecessor element and a follower element.
  • Linear lists are sequences of finite elements, such as one-dimensional arrays and linked lists.
  • Each data element may consist of several data items.

The mathematical expression can be as follows:
( a 1 , a 2 , . . . . , an − 1 , an ) (a_1,a_2,....,a_{n-1},a_n)(a1,a2,....,an1,an)
**The data element n is the length of the linear table. ** You can also see that each element has an exact position, and the length can also grow and shorten as needed.

The linear table introduced in this article takes the one that comes with linux sys/queue.has an example, and the list comes from a header file in FreeBSD. Generally carried by glibc. The source code viewing link is as follows:

The header file queue.h provides a more standard programming interface for linked lists in C language. Today's version is mostly the 8.5 version of the University of California, Berkeley in August 1994.

1.1 Function introduction of different queues

A singly-linked list is headed by a single forward pointer. The elements are singly linked for minimum space and pointer manipulation overhead at the expense of O(n) removal for arbitrary elements. New elements can be added to the list after an existing element or at the head of the list. Elements being removed from the head of the list should use the explicit macro for this purpose for optimum efficiency. A singly-linked list may only be traversed in the forward direction. Singly-linked lists are ideal for applications with large datasets and few or no removals or for implementing a LIFO queue.

The head of the singly linked list (SLIST) is a forward pointer. Elements are singly linked, allowing O(n) removal for arbitrary elements at the cost of minimal space and pointer manipulation overhead. New elements can be added after existing elements or at the head of the list. Elements removed from the head of the list should use explicit macros for this purpose for best efficiency. A singly linked list can only be traversed in the forward direction. Singly linked lists are suitable for applications with large data sets and few or no deletions, or to implement last-in-first-out queues.

A singly-linked tail queue is headed by a pair of pointers, one to the head of the list and the other to the tail of the list. The elements are singly linked for minimum space and pointer manipulation overhead at the expense of O(n) removal for arbitrary elements. New elements can be added to the list after an existing element, at the head of the list, or at the end of the list. Elements being removed from the head of the tail queue should use the explicit macro for this purpose for optimum efficiency. A singly-linked tail queue may only be traversed in the forward direction. Singly-linked tail queues are ideal for applications with large datasets and few or no removals or for implementing a FIFO queue.

A single-chain tail queue (STAILQ) is guided by a pair of pointers, one pointing to the head of the list and the other pointing to the tail of the list. Elements are singly linked, allowing O(n) removal for arbitrary elements at the cost of minimal space and pointer manipulation overhead. New elements can be added to the list, after existing elements, at the head of the list, or at the end of the list. Elements to be removed from the head of the tail queue should use explicit macros for this purpose for best efficiency. Single-tailed queues can only be traversed in the forward direction. Single-link tail queues are ideal for applications with large data sets and little or no deletion or implementing FIFO queues.

Some bsds provide SIMPLEQ instead of STAILQ. They are the same, but they are named differently on different BSDs for historical reasons. STAILQ originated from FreeBSD, while SIMPLEQ originated from NetBSD. For compatibility, some systems provide two sets of macros. Glibc provides STAILQ and SIMPLEQ, which are identical except for the lack of a SIMPLEQ equivalent to STAILQ_CONCAT() .

A list is headed by a single forward pointer (or an array of forward pointers for a hash table header). The elements are doubly linked so that an arbitrary element can be removed without a need to traverse the list. New elements can be added to the list before or after an existing element or at the head of the list. A list may be traversed in either direction.

The head of a list (LIST) consists of a single forward pointer (or an array of forward pointers for a hash table header). Elements are doubly linked, so arbitrary elements can be removed without traversing the list. New elements can be added to the list, before or after existing elements, or at the head of the list. Lists can be traversed in either direction.

A tail queue is headed by a pair of pointers, one to the head of the list and the other to the tail of the list. The elements are doubly linked so that an arbitrary element can be removed without a need to traverse the list. New elements can be added to the list before or after an existing element, at the head of the list, or at the end of the list. A tail queue may be traversed in either direction.

The tail queue (TAILQ) is guided by a pair of pointers, one pointing to the head of the list and the other pointing to the tail of the list. Elements are doubly linked, so arbitrary elements can be removed without traversing the list. New elements can be added to the list, before or after existing elements, at the beginning or end of the list. The tail queue can be traversed in either direction.

Finally, there is a circular queue of CIRCLEQ, which is generally not needed. The above four queues are enough for use.

There are five types of data structures in total: singly linked list, list, singly linked tail queue, and tail queue. All five structures support the following functions:

  • Insert a new entry at the head of the list.

  • Inserts a new entry after any element in the list.

  • Remove an item from the head of the list.

  • Traverse the list forward.

All doubly-linked types of data structures (lists and tailqueues) also allow:

  • Insert a new entry before any element in the list.

  • Delete any entries in the list.

However:

  • Each element requires two pointers instead of one.

  • The code size and execution time of operations (except removal) are about twice that of singly-linked data structures.

  • The list is the simplest double-linked data structure, and only the above functions are supported on the single-linked list.

The tail queue adds the following functionality:

  • Entries can be added at the end of the list.

  • They can be traversed in reverse, which comes at a price.

However:

  • All list insertions and deletions must specify the head of the list.

  • Each header entry requires two pointers instead of one.

  • The code size is 15% larger than a singly linked list, and operations run 20% slower than a singly linked list.

Another type of data structure—circular queues—violates the C language's aliasing rules, causing compilation errors. All code that uses them should be converted to another structure; tail queues are usually the easiest to convert.

1.2 Function Comparison Table

Here are some feature flags:

  • + means that this macro is available.
  • -means that this macro is not available.
  • smeans this macro is available but at a lower rate (o(n) complexity)
function support SLIST LIST STAILQ TAILQ
_HEAD + + + +
_CLASS_HEAD + + + +
_HEAD_INITIALIZER + + + +
_ENTRY + + + +
_CLASS_ENTRY + + + +
_INIT + + + +
_EMPTY + + + +
_END + + + +
_FIRST + + + +
_NEXT + + + +
_PREV - + - +
_LAST - - + +
_LAST_FAST - - - +
_FOREACH + + + +
_FOREACH_FROM + + + +
_FOREACH_SAFE + + + +
_FOREACH_FROM_SAFE + + + +
_FOREACH_REVERSE - - - +
_FOREACH_REVERSE_FROM - - - +
_FOREACH_REVERSE_SAFE - - - +
_FOREACH_REVERSE_FROM_SAFE - - - +
_INSERT_HEAD + + + +
_INSERT_BEFORE - + - +
_INSERT_AFTER + + + +
_INSERT_TAIL - - + +
_CONCAT s s + +
_REMOVE_AFTER + - + -
_REMOVE_HEAD + - + -
_REMOVE s + s +
_SWAP + + + +

2. Specific instructions

2.1 Detailed introduction of SLIST one-way list

SLIST in BSD queue.h is a singly linked list that provides a simple and efficient data structure suitable for many common program scenarios. The following is a summary of SLIST in terms of efficiency, security and usage:

  1. Efficiency: SLIST is very efficient because it is a very simple data structure, and its insertion, deletion, and traversal operations can all be completed in constant time. This makes SLIST very suitable for program scenarios that require high efficiency, such as operating system kernels, network servers, etc.
  2. Security: In a multi-threaded environment, synchronization and mutual exclusion are usually required for concurrent access to the same SLIST linked list to ensure data integrity and correctness. Therefore, when using SLIST in a multi-threaded environment, lock protection is indeed required.
  3. How to use: It is very simple to use SLIST, just include the header file <sys/queue.h>. SLIST provides a series of macros that can be used to manipulate linked lists, such as SLIST_INIT, SLIST_INSERT_HEAD, SLIST_REMOVE, etc. These macros can be used directly in the code, which is very convenient. In addition, SLIST also provides some auxiliary macros, such as SLIST_EMPTY and SLIST_NEXT, etc., which can be used to query the state of the linked list and access the elements of the linked list.

In general, SLIST in BSD queue.h is a very efficient, reliable, and easy-to-use data structure, suitable for many common program scenarios. During program development, programmers can use SLIST to improve program efficiency and security while reducing development time and code complexity.

The head of a singly linked list is a structure defined by the SLIST_HEAD() macro. This structure contains a pointer to the first element of the list. Elements are singly linked, allowing O(n) removal for arbitrary elements at the cost of minimal space and pointer manipulation overhead. New elements can be added after existing elements or at the head of the list. The declaration of the SLIST_HEAD structure is as follows:

#define SLIST_HEAD(name, type)                      \
    struct name {                                   \
        struct type *slh_first; /* first element */ \
    }

SLIST_HEAD(HEADNAME, TYPE) head;

where HEADNAME is the name of the structure to be defined and struct TYPE is the type of element to be linked into the list. A pointer to the head of the list can later be declared as:

struct HEADNAME *headp;

The HEADNAME feature is generally not used, resulting in the following weird code (using an anonymous struct):

SLIST_HEAD(, TYPE) head, *headp;

The following are supporting other operation functions:

  • The SLIST_ENTRY() macro declares a structure that links the elements of a list.

  • The SLIST_INIT() macro initializes the list referenced by head.

  • Lists can also be initialized statically by using the SLIST_HEAD_INITIALIZER() macro, as follows:

    SLIST_HEAD(HEADNAME, TYPE) head = SLIST_HEAD_INITIALIZER(head);
    
  • The SLIST_INSERT_HEAD() macro inserts the new element elm at the head of the list.

  • The SLIST_INSERT_AFTER() macro inserts the new element elm after the element listelm.

  • The SLIST_REMOVE_HEAD() macro removes the first element of the list pointed to by head.

  • The SLIST_REMOVE_AFTER() macro deletes list elements after elm.

  • The SLIST_REMOVE() macro removes the element elm from the list pointed to by head.

  • The SLIST_FIRST() and SLIST_NEXT() macros can be used to iterate over lists:

    for (np = SLIST_FIRST(&head); np != NULL; np = SLIST_NEXT(np, FIELDNAME))
    

Or, for simplicity, the SLIST_FOREACH() macro can be used:

SLIST_FOREACH(np, head, FIELDNAME)

The macro SLIST_FOREACH_SAFE() traverses the list referenced by head forward, assigning each element to var in turn. However, unlike SLIST_FOREACH() , SLIST_FOREACH_SAFE() allows to delete a var and release it safely in a loop without interfering with traversal.

The SLIST_EMPTY() macro should be used to check if a simple list is empty.

SLIST_FOREACH() does not allow removing or freeing vars in the loop because it would interfere with traversal. SLIST_FOREACH_SAFE() exists in bsd but not glibc, and it fixes this limitation by allowing vars to be safely removed from the list and released from the loop without interfering with traversal.

The following is the specific macro function definition type display:

#include <sys/queue.h>

SLIST_ENTRY(TYPE);

SLIST_HEAD(HEADNAME, TYPE);
SLIST_HEAD SLIST_HEAD_INITIALIZER(SLIST_HEAD head);
void SLIST_INIT(SLIST_HEAD *head);

int SLIST_EMPTY(SLIST_HEAD *head);

void SLIST_INSERT_HEAD(SLIST_HEAD *head,
                       struct TYPE *elm, SLIST_ENTRY NAME);
void SLIST_INSERT_AFTER(struct TYPE *listelm,
                       struct TYPE *elm, SLIST_ENTRY NAME);

struct TYPE *SLIST_FIRST(SLIST_HEAD *head);
struct TYPE *SLIST_NEXT(struct TYPE *elm, SLIST_ENTRY NAME);

SLIST_FOREACH(struct TYPE *var, SLIST_HEAD *head, SLIST_ENTRY NAME);

void SLIST_REMOVE(SLIST_HEAD *head, struct TYPE *elm,
                       SLIST_ENTRY NAME);
void SLIST_REMOVE_HEAD(SLIST_HEAD *head,
                       SLIST_ENTRY NAME);
2.2 SLIST one-way list usage example

SLIST in BSD queue.h is an implementation of a singly linked list for managing data structures in C. Using SLIST requires the following steps:

  1. Define a structure that should contain a SLIST_ENTRY member to link the structure into a linked list.
  2. Use the SLIST_ENTRY macro to define a structure member to link the structure into a linked list.
  3. Use the SLIST_HEAD macro to define a linked list header that contains a SLIST_ENTRY member.
  4. Use the SLIST_INIT macro to initialize the linked list head to an empty linked list.
  5. Use macros such as SLIST_INSERT_HEAD, SLIST_INSERT_AFTER, SLIST_REMOVE to manipulate elements in the linked list.

The main difference between SLIST and LIST is that SLIST is a one-way linked list, which can only insert and delete elements from the head, while LIST is a doubly linked list, which can insert and delete elements from the head and tail. When using SLIST in BSD queue.h, you need to pay attention to the following:

  1. Bidirectional traversal is not supported: SLIST is a one-way linked list, which can only be traversed from the head and cannot be traversed in reverse.
  2. Does not support random access: SLIST can only insert and delete elements from the head, so elements in the linked list cannot be directly accessed through indexes or pointers.
  3. Unsafe: SLIST does not perform out-of-bounds checks, so there is a pointer out-of-bounds problem, and security needs to be paid attention to.
  4. Does not support dynamic memory allocation: SLIST does not support dynamic memory allocation at runtime, so the size of the linked list needs to be determined at compile time.
  5. Applicability limitations: SLIST is only applicable to the data structure of singly linked list, so it is not applicable to other types of data structures.

In general, SLIST in BSD queue.h is a simple and efficient implementation of singly linked list. When using it, you need to pay attention to its applicability restrictions and safety issues.

Here is an example of usage:

SLIST_HEAD(listhead, entry) head;
struct entry {
    
    
	...
	SLIST_ENTRY(entry) entries;	/* Simple list. */
	...
} *n1, *n2, *np;

SLIST_INIT(&head);			/* Initialize simple list. */

n1 = malloc(sizeof(struct entry));	/* Insert at the head. */
SLIST_INSERT_HEAD(&head, n1, entries);

n2 = malloc(sizeof(struct entry));	/* Insert after. */
SLIST_INSERT_AFTER(n1, n2, entries);

SLIST_FOREACH(np, &head, entries)	/* Forward traversal. */
	np-> ...

while (!SLIST_EMPTY(&head)) {
    
    	 	/* Delete. */
	n1 = SLIST_FIRST(&head);
	SLIST_REMOVE_HEAD(&head, entries);
	free(n1);
}
2.3 Detailed introduction of LIST bidirectional list

The head of the list is a structure defined by the LIST_HEAD() macro. This structure contains a pointer to the first element of the list. Elements are doubly linked, so arbitrary elements can be removed without traversing the list. New elements can be added to the list, after existing elements, before existing elements, or at the head of the list. The declaration of the LIST_HEAD structure is as follows:

LIST_HEAD(HEADNAME, TYPE) head;

where HEADNAME is the name of the structure to be defined and struct TYPE is the type of element to be linked into the list. A pointer to the head of the list can later be declared as:

struct HEADNAME *headp; # (名称head和headp是用户可选择的。)

The HEADNAME feature is generally not used, resulting in the following strange code:

LIST_HEAD(, TYPE) head, *head;

has the following operations:

  • The LIST_ENTRY() macro declares a structure linking the elements of a list.

  • The LIST_INIT() macro initializes the list referenced by head.

  • Lists can also be initialized statically by using the LIST_HEAD_INITIALIZER() macro, as follows:

    LIST_HEAD(HEADNAME, TYPE) head = LIST_HEAD_INITIALIZER(head);
    
  • The LIST_INSERT_HEAD() macro inserts the new element elm at the head of the list.

  • The LIST_INSERT_AFTER() macro inserts the new element elm after the element listelm.

  • The LIST_INSERT_BEFORE() macro inserts the new element elm before the element listelm.

  • The LIST_REMOVE() macro removes the element elm from the list.

  • The LIST_REPLACE() macro replaces the list element elm with a new element elm2.

  • The LIST_FIRST() and LIST_NEXT() macros can be used to iterate over lists:

    for (np = LIST_FIRST(&head);np != NULL;np = LIST_NEXT(np, FIELDNAME))
    

Or, for simplicity, the LIST_FOREACH() macro can be used:

LIST_FOREACH(np, head, FIELDNAME)

The macro LIST_FOREACH_SAFE() traverses the list referenced by head forward, and assigns each element to var in turn. However, unlike LIST_FOREACH(), it allows the var to be removed and released safely from the loop without interfering with traversal.

The LIST_EMPTY() macro should be used to check if the list is empty.

The use of a two-way list is not much different from that of a one-way list. The following is the prototype of the macro function definition:

#include <sys/queue.h>

LIST_ENTRY(TYPE);

LIST_HEAD(HEADNAME, TYPE);
LIST_HEAD LIST_HEAD_INITIALIZER(LIST_HEAD head);
void LIST_INIT(LIST_HEAD *head);

int LIST_EMPTY(LIST_HEAD *head);

void LIST_INSERT_HEAD(LIST_HEAD *head,
                       struct TYPE *elm, LIST_ENTRY NAME);
void LIST_INSERT_BEFORE(struct TYPE *listelm,
                       struct TYPE *elm, LIST_ENTRY NAME);
void LIST_INSERT_AFTER(struct TYPE *listelm,
                       struct TYPE *elm, LIST_ENTRY NAME);

struct TYPE *LIST_FIRST(LIST_HEAD *head);
struct TYPE *LIST_NEXT(struct TYPE *elm, LIST_ENTRY NAME);

LIST_FOREACH(struct TYPE *var, LIST_HEAD *head, LIST_ENTRY NAME);

void LIST_REMOVE(struct TYPE *elm, LIST_ENTRY NAME);

Here is a usage example:

LIST_HEAD(listhead, entry) head;
struct entry {
	...
	LIST_ENTRY(entry) entries;	/* List. */
	...
} *n1, *n2, *np;

LIST_INIT(&head);			/* Initialize list. */

n1 = malloc(sizeof(struct entry));	/* Insert at the head. */
LIST_INSERT_HEAD(&head, n1, entries);

n2 = malloc(sizeof(struct entry));	/* Insert after. */
LIST_INSERT_AFTER(n1, n2, entries);

n2 = malloc(sizeof(struct entry));	/* Insert before. */
LIST_INSERT_BEFORE(n1, n2, entries);
					/* Forward traversal. */
LIST_FOREACH(np, &head, entries)
	np-> ...

while (!LIST_EMPTY(&head)) {		/* Delete. */
	n1 = LIST_FIRST(&head);
	LIST_REMOVE(n1, entries);
	free(n1);
}
2.4 Detailed introduction of STAILQ one-way tail queue

A singly-linked tail queue is headed by a structure defined by the STAILQ_HEAD() macro. This structure contains a pair of pointers, one pointing to the first element in the tail queue and the other pointing to the last element in the tail queue. Elements are singly linked, allowing O(n) removal for arbitrary elements at the cost of minimal space and pointer manipulation overhead. New elements can be added to the tail queue, after existing elements, at the head of the tail queue, or at the end of the tail queue. The STAILQ_HEAD structure is declared as follows:

STAILQ_HEAD(HEADNAME, TYPE) head;

where HEADNAME is the name of the structure to be defined and struct TYPE is the type of element to be linked to the tail queue. A pointer to the head of the tail queue can later be declared as:

struct HEADNAME *headp;		# (名称head和headp是用户可选择的。)

Here are some common operations:

  • The STAILQ_ENTRY() macro declares a structure that links the elements in the tail queue.

  • The STAILQ_INIT() macro initializes the tail queue referenced by head.

  • Tail queues can also be statically initialized using the STAILQ_HEAD_INITIALIZER() macro.

  • The STAILQ_INSERT_AFTER() macro inserts the new element elm after the element listelm.

  • The STAILQ_INSERT_HEAD() macro inserts the new element elm at the head of the tail queue.

  • The STAILQ_INSERT_TAIL() macro inserts the new element elm at the end of the tail queue.

  • The STAILQ_REMOVE_AFTER() macro deletes the queue element immediately following elm. Unlike STAILQ_REMOVE, this macro does not traverse the entire tail queue.

  • The STAILQ_REMOVE_HEAD() macro removes the first element from the tail queue. For best efficiency, elements removed from the head of the tail queue should explicitly use this macro instead of the generic STAILQ_REMOVE macro.

  • The STAILQ_REMOVE() macro removes the element elm from the tail queue. This macro should be avoided as it iterates over the entire list. If this macro is needed in a code path with high usage, or when operating on a long-tailed queue, then a doubly-linked tailed queue should be used.

  • The STAILQ_CONCAT() macro concatenates all elements of the tail queue referenced by head2 to the end of the tail queue referenced by head1, emptying head2 in-process. This is more efficient than removing and inserting individual elements, since it doesn't actually traverse head2.

    The STAILQ_FOREACH() macro is used for queue traversal:

The STAILQ_FOREACH() macro is used for queue traversal, and the macro STAILQ_FOREACH_SAFE() traverses the queue referenced by head forward, and assigns each element to var in turn. However, unlike STAILQ_FOREACH(), it allows to delete var and release it safely in the loop without interfering with traversal.

The tail queue or any part of the tail queue can be manually traversed using the STAILQ_FIRST(), STAILQ_NEXT(), and STAILQ_LAST() macros. The STAILQ_EMPTY() macro should be used to check if the tail queue is empty.

The following are the macro function prototype definitions of these functions:

#include <sys/queue.h>

STAILQ_ENTRY(TYPE);

STAILQ_HEAD(HEADNAME, TYPE);
STAILQ_HEAD STAILQ_HEAD_INITIALIZER(STAILQ_HEAD head);
void STAILQ_INIT(STAILQ_HEAD *head);

int STAILQ_EMPTY(STAILQ_HEAD *head);

void STAILQ_INSERT_HEAD(STAILQ_HEAD *head,
                        struct TYPE *elm, STAILQ_ENTRY NAME);
void STAILQ_INSERT_TAIL(STAILQ_HEAD *head,
                        struct TYPE *elm, STAILQ_ENTRY NAME);
void STAILQ_INSERT_AFTER(STAILQ_HEAD *head, struct TYPE *listelm,
                        struct TYPE *elm, STAILQ_ENTRY NAME);

struct TYPE *STAILQ_FIRST(STAILQ_HEAD *head);
struct TYPE *STAILQ_NEXT(struct TYPE *elm, STAILQ_ENTRY NAME);

STAILQ_FOREACH(struct TYPE *var, STAILQ_HEAD *head, STAILQ_ENTRY NAME);

void STAILQ_REMOVE(STAILQ_HEAD *head, struct TYPE *elm, TYPE,
                        STAILQ_ENTRY NAME);
void STAILQ_REMOVE_HEAD(STAILQ_HEAD *head,
                        STAILQ_ENTRY NAME);

void STAILQ_CONCAT(STAILQ_HEAD *head1, STAILQ_HEAD *head2);

Here is an example of usage:

STAILQ_HEAD(listhead, entry) head = STAILQ_HEAD_INITIALIZER(head);
struct entry {
	...
	STAILQ_ENTRY(entry) entries;	/* Singly-linked tail queue. */
	...
} *n1, *n2, *np;

n1 = malloc(sizeof(struct entry));	/* Insert at the head. */
STAILQ_INSERT_HEAD(&head, n1, entries);

n2 = malloc(sizeof(struct entry));	/* Insert at the tail. */
STAILQ_INSERT_TAIL(&head, n2, entries);

n2 = malloc(sizeof(struct entry));	/* Insert after. */
STAILQ_INSERT_AFTER(&head, n1, n2, entries);

					/* Deletion. */
STAILQ_REMOVE(&head, n2, entry, entries);
free(n2);
					/* Deletion from the head. */
n3 = STAILQ_FIRST(&head);
STAILQ_REMOVE_HEAD(&head, entries);
free(n3);
					/* Forward traversal. */
STAILQ_FOREACH(np, &head, entries)
	np-> ...
					/* Safe forward traversal. */
STAILQ_FOREACH_SAFE(np, &head, entries, np_temp) {
	np-> ...
	STAILQ_REMOVE(&head, np, entry, entries);
	free(np);
}
					/* Delete. */
while (!STAILQ_EMPTY(&head)) {
	n1 = STAILQ_FIRST(&head);
	STAILQ_REMOVE_HEAD(&head, entries);
	free(n1);
}
2.5 Detailed introduction of TAILQ two-way tail queue

The head of the tail queue consists of structures defined by the TAILQ_HEAD() macro. This structure contains a pair of pointers, one pointing to the first element in the tail queue and the other pointing to the last element in the tail queue. Elements are doubly linked, so arbitrary elements can be removed without traversing the tail queue. New elements can be added to the queue, after existing elements, before existing elements, at the head of the queue, or at the end of the queue. A TAILQ_HEAD structure is declared as follows:

TAILQ_HEAD(HEADNAME, TYPE) head;

where HEADNAME is the name of the structure to be defined and struct TYPE is the type of element to be linked to the tail queue. A pointer to the head of the tail queue can later be declared as:

struct HEADNAME *headp; // (名称head和headp是用户可选择的。)

Here are some actions:

  • The TAILQ_ENTRY() macro declares a structure that links the elements in the tail queue.

  • The TAILQ_INIT() macro initializes the tail queue referenced by head.

  • Tail queues can also be statically initialized using the TAILQ_HEAD_INITIALIZER() macro.

  • The TAILQ_INSERT_HEAD() macro inserts the new element elm at the head of the tail queue.

  • The TAILQ_INSERT_TAIL() macro inserts the new element elm at the end of the tail queue.

  • The TAILQ_INSERT_AFTER() macro inserts the new element elm after the element listelm.

  • The TAILQ_INSERT_BEFORE() macro inserts the new element elm before the element listelm.

  • The TAILQ_REMOVE() macro removes the element elm from the tail queue.

  • The TAILQ_REPLACE() macro replaces the list element elm with a new element elm2.

  • The TAILQ_CONCAT() macro concatenates all elements of the tail queue referenced by head2 to the end of the tail queue referenced by head1, emptying head2 in-process. This is more efficient than removing and inserting individual elements, since it doesn't actually traverse head2.

  • TAILQ_FOREACH() and TAILQ_FOREACH_REVERSE() are used to traverse the tail queue. TAILQ_FOREACH() starts from the first element and goes to the last element. TAILQ_FOREACH_REVERSE() starts from the last element and advances to the first element.

The macros TAILQ_FOREACH_SAFE() and TAILQ_FOREACH_REVERSE_SAFE() traverse the list referenced by head forward or reverse respectively, and assign each element to var in turn. However, unlike their unsafe counterparts, they allow both deleting a var and freeing it safely in a loop without interfering with traversal.

The tail queue or any part of the tail queue can be manually traversed using the TAILQ_FIRST(), TAILQ_NEXT(), TAILQ_LAST(), and TAILQ_PREV() macros.

The TAILQ_EMPTY() macro should be used to check if the tail queue is empty

Here is the function prototype:

#include <sys/queue.h>

TAILQ_ENTRY(TYPE);

TAILQ_HEAD(HEADNAME, TYPE);
TAILQ_HEAD TAILQ_HEAD_INITIALIZER(TAILQ_HEAD head);
void TAILQ_INIT(TAILQ_HEAD *head);

int TAILQ_EMPTY(TAILQ_HEAD *head);

void TAILQ_INSERT_HEAD(TAILQ_HEAD *head,
                        struct TYPE *elm, TAILQ_ENTRY NAME);
void TAILQ_INSERT_TAIL(TAILQ_HEAD *head,
                        struct TYPE *elm, TAILQ_ENTRY NAME);
void TAILQ_INSERT_BEFORE(struct TYPE *listelm,
                        struct TYPE *elm, TAILQ_ENTRY NAME);
void TAILQ_INSERT_AFTER(TAILQ_HEAD *head, struct TYPE *listelm,
                        struct TYPE *elm, TAILQ_ENTRY NAME);

struct TYPE *TAILQ_FIRST(TAILQ_HEAD *head);
struct TYPE *TAILQ_LAST(TAILQ_HEAD *head, HEADNAME);
struct TYPE *TAILQ_PREV(struct TYPE *elm, HEADNAME, TAILQ_ENTRY NAME);
struct TYPE *TAILQ_NEXT(struct TYPE *elm, TAILQ_ENTRY NAME);

TAILQ_FOREACH(struct TYPE *var, TAILQ_HEAD *head,
                        TAILQ_ENTRY NAME);
TAILQ_FOREACH_REVERSE(struct TYPE *var, TAILQ_HEAD *head, HEADNAME,
                        TAILQ_ENTRY NAME);

void TAILQ_REMOVE(TAILQ_HEAD *head, struct TYPE *elm,
                        TAILQ_ENTRY NAME);

void TAILQ_CONCAT(TAILQ_HEAD *head1, TAILQ_HEAD *head2,
                        TAILQ_ENTRY NAME);

Here is a practical example:

TAILQ_HEAD(tailhead, entry) head;
struct entry {
	...
	TAILQ_ENTRY(entry) entries;	/* Tail queue. */
	...
} *n1, *n2, *np;

TAILQ_INIT(&head);			/* Initialize queue. */

n1 = malloc(sizeof(struct entry));	/* Insert at the head. */
TAILQ_INSERT_HEAD(&head, n1, entries);

n1 = malloc(sizeof(struct entry));	/* Insert at the tail. */
TAILQ_INSERT_TAIL(&head, n1, entries);

n2 = malloc(sizeof(struct entry));	/* Insert after. */
TAILQ_INSERT_AFTER(&head, n1, n2, entries);

n2 = malloc(sizeof(struct entry));	/* Insert before. */
TAILQ_INSERT_BEFORE(n1, n2, entries);
					/* Forward traversal. */
TAILQ_FOREACH(np, &head, entries)
	np-> ...
					/* Manual forward traversal. */
for (np = n2; np != NULL; np = TAILQ_NEXT(np, entries))
	np-> ...
					/* Delete. */
while ((np = TAILQ_FIRST(&head))) {
	TAILQ_REMOVE(&head, np, entries);
	free(np);
}

Guess you like

Origin blog.csdn.net/Once_day/article/details/130913986