佇列的C語言實現(通過核心連結串列)
0. 環境說明
本文的實驗環境是:
win7作業系統+Keil 5 IDE.
非常適合嵌入式軟體開發
1. 打造自己的“list.h”
在微控制器程式開發中,有時候會用到佇列。能否設計一個通用的佇列呢?我想,可以把核心連結串列用起來。
以下程式碼是我從核心裡面扒拉出來的,再稍微改改,就可以在工程中使用了。
#ifndef _LIST_H
#define _LIST_H
/*
/usr/src/linux-headers-4.8.0-36-generic/include/linux/stddef.h
*/
//求偏移量
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0 )->MEMBER)
/*
/usr/src/linux-headers-4.8.0-36-generic/include/linux/kernel.h
*/
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
//小指標轉大指標
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
/*
/usr/src/linux-headers-4.8.0-36-generic/include/linux/types.h
*/
struct list_head {
struct list_head *next, *prev;
};
/*
/usr/src/linux-headers-4.8.0-36-generic/include/linux/list.h
*/
#define LIST_HEAD_INIT(name) { &(name), &(name) }
//以下這個巨集用來定義並且初始化頭結點
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
//這個函式不知道核心裡面有沒有,我自己加的
static inline void node_init(struct list_head *node)
{
node->next = node;
node->prev = node;
}
/* kernel 3.14 */
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new; // kernel 4.8中 這句話是 WRITE_ONCE(prev->next, new);
}
/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next); //頭插
}
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head); //尾插
}
/*
* Delete a list entry by making the prev/next entries
* point to each other.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next; //WRITE_ONCE(prev->next, next);
}
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
node_init(entry); //add by me
//entry->next = LIST_POISON1;
//entry->prev = LIST_POISON2;
}
/**
* list_empty - tests whether a list is empty
* @head: the list to test.
*/
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
//return READ_ONCE(head->next) == head;
}
/**
* list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
/**
* list_for_each_safe - iterate over a list safe against removal of list entry
* @pos: the &struct list_head to use as a loop cursor.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_head within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/**
* list_first_entry - get the first element from a list
* @ptr: the list head to take the element from.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_head within the struct.
*
* Note, that list is expected to be not empty.
*/
#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)
/**
* list_next_entry - get the next element in list
* @pos: the type * to cursor
* @member: the name of the list_head within the struct.
*/
#define list_next_entry(pos, member) \
list_entry((pos)->member.next, typeof(*(pos)), member)
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member); \
&pos->member != (head); \
pos = list_next_entry(pos, member))
/**
* list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*/
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member), \
n = list_next_entry(pos, member); \
&pos->member != (head); \
pos = n, n = list_next_entry(n, member))
/**
* list_for_each_entry_from - iterate over list of given type from the current point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*
* Iterate over list of given type, continuing from current position.
*/
//從pos指向的結構體開始遍歷
#define list_for_each_entry_from(pos, head, member) \
for (; &pos->member != (head); \
pos = list_next_entry(pos, member))
/**
* list_for_each_entry_safe_from - iterate over list from current point safe against removal
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*
* Iterate over list of given type from current point, safe against
* removal of list entry.
*/
#define list_for_each_entry_safe_from(pos, n, head, member) \
for (n = list_next_entry(pos, member); \
&pos->member != (head); \
pos = n, n = list_next_entry(n, member))
/**
* list_for_each_entry_continue - continue iteration over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*
* Continue to iterate over list of given type, continuing after
* the current position.
*/
//從pos的下一個開始遍歷
#define list_for_each_entry_continue(pos, head, member) \
for (pos = list_next_entry(pos, member); \
&pos->member != (head); \
pos = list_next_entry(pos, member))
/**
* list_for_each_entry_safe_continue - continue list iteration safe against removal
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*
* Iterate over list of given type, continuing after current point,
* safe against removal of list entry.
*/
#define list_for_each_entry_safe_continue(pos, n, head, member) \
for (pos = list_next_entry(pos, member), \
n = list_next_entry(pos, member); \
&pos->member != (head); \
pos = n, n = list_next_entry(n, member))
#endif
2. 介面設計
#ifndef _QUEUE_H
#define _QUEUE_H
#include "list.h"
struct queue_info {
struct list_head *head;
void (*push)(struct queue_info *info, struct list_head *new_node);
struct list_head *(*top)(struct queue_info *info);
struct list_head *(*pop)(struct queue_info *info);
int (*for_each)(struct queue_info *info, void (*todo)(struct list_head *node));
int (*is_empty)(struct queue_info *info);
};
void queue_init(struct queue_info *info,struct list_head * head);
void queue_destroy(struct queue_info *info);
#endif
struct queue_info
中,首先有一個struct list_head *head
,這是一個指標,指向連結串列的頭結點。這個頭結點需要使用者分配空間;其次是佇列具有的方法(指向函式的指標)。
void (*push)(struct queue_info *info, struct list_head *new_node);
入隊操作struct list_head *(*top)(struct queue_info *info);
得到佇列的首元素(有別於出隊)struct list_head *(*pop)(struct queue_info *info);
出隊int (*for_each)(struct queue_info *info, void (*todo)(struct list_head *node));
遍歷佇列,todo由使用者實現int (*is_empty)(struct queue_info *info);
判斷佇列是否為空
3. 具體實現
3.1 入隊
static void queue_push (struct queue_info *info, struct list_head *new_node)
{
list_add_tail(new_node,info->head);
}
直接呼叫核心連結串列的尾插函式list_add_tail
就行。
3.2 得到隊首的元素
struct list_head *queue_top(struct queue_info *info)
{
if (queue_is_empty(info)) {
return NULL; //佇列為空
}
else{
return info->head->next;
}
}
因為是通用佇列,無法預測佇列中元素的資料形態,所以返回指向struct list_head
的指標。為了得到資料,需要使用者自己轉換(通過巨集container_of
).
3.3 出隊
struct list_head *queue_pop(struct queue_info *info)
{
if (queue_is_empty(info)) {
return NULL; //佇列為空
}
else{
struct list_head *temp = info->head->next;
list_del(temp); //刪除佇列的首元素
return temp;
}
}
注意,list_del(temp);
這句話僅僅使隊首元素脫離連結串列,隊首元素的空間需要使用者自己回收。
3.4 遍歷佇列的每個元素
static int queue_for_each(struct queue_info *info, void (*todo)(struct list_head *node))
{
if(queue_is_empty(info)){
printf("the queue is empty\n");
return -1;
}
else{
struct list_head *pos = NULL;
struct list_head *n = NULL;
list_for_each_safe(pos, n, info->head)
todo(pos);
return 0;
}
}
如果佇列為空,打印出錯資訊並返回-1;否則,呼叫使用者傳入的todo
函式,對每個元素進行操作(list_for_each_safe
是核心連結串列的安全遍歷,用普通遍歷也是可以的,因為todo
函式一般不會進行刪除。刪除沒有道理啊,我實在想不出應用場景)。
其實,我設計這個介面的初衷是為了測試,比如列印佇列每個元素,看看入隊順序是否正確等。
3.5 佇列的初始化
void queue_init(struct queue_info *info,struct list_head * head)
{
info->head = head;
node_init(head); //頭結點的next和prev都指向自身
info->push = queue_push;
info->pop = queue_pop;
info->top = queue_top;
info->is_empty = queue_is_empty;
info->for_each = queue_for_each;
}
此函式應該在最初呼叫。使用者需要定義struct queue_info
結構體和struct list_head
結構體,然後傳入二者的地址。
3.6 佇列的析構
void queue_destroy(struct queue_info *info)
{
}
我想了想,覺得此函式只能為空。理由是:
1. 不需要回收空間,所以真的沒啥可以做的;
2. 本打算不斷出隊直到為空,發現出隊是使用者的事情,不需要越俎代庖。
4. 測試程式碼及結果
#include "stdio.h"
#include "queue.h"
#define NAME_MAX_LEN 20
struct data_info {
char name[NAME_MAX_LEN];
int age;
struct list_head list;
};
//此函式用於列印結點資訊
void print_node(struct list_head *node)
{
struct data_info *pdata;
pdata = container_of(node, struct data_info, list);
printf("name:%s, age:%d\n",pdata->name, pdata->age);
}
int main(void)
{
struct data_info s[] = {
{"A", 34},
{"B", 42},
{"C", 36},
{"D", 100},
{"E", 18},
};
struct list_head head;
struct queue_info queue;
queue_init(&queue,&head);
//測試入隊
int i;
for (i = 0; i < sizeof s/ sizeof s[0]; ++i)
{
queue.push(&queue,&s[i].list);
}
//測試遍歷
queue.for_each(&queue,print_node);
//測試top
printf("top method\n");
struct list_head *p_node = queue.top(&queue);
if(p_node==NULL){
printf("top test failed\n");
}
else{
print_node(p_node);
}
//再次遍歷,驗證top並不是出隊
queue.for_each(&queue,print_node);
//測試出隊
while(!queue.is_empty(&queue)){
p_node = queue.pop(&queue);
printf("out queue:");
print_node(p_node);
}
while(1);//微控制器程式,沒有作業系統,只能在這裡死迴圈了
}
在keil5上除錯(use simulator),測試結果截圖如下:
5. 需要注意的問題
普通的編譯器是無法編譯list.h
的,必須支援gnu語法才行。對於Keil,可以新增對gnu的支援,如下圖,輸入--gnu
.
【完】