队列的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))#endif2. 接口設計
#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);#endifstruct 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 20struct 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);//測試topprintf("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.
【完】
總結
以上是生活随笔為你收集整理的队列的C语言实现(通过内核链表)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序员面试系列——大小端
- 下一篇: Keil Debug(printf) V