1. 程式人生 > >佇列的C語言實現(通過核心連結串列)

佇列的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.

這裡寫圖片描述

【完】