第一次在掘金上写文章,本文旨在记录学习链表的一些心得,不当之处欢迎各位大佬指正
文章引用,着重讲解container_of,本文底层思想也是基于该宏
简介
本示例一共用到三种不同的结构体,然后使用链表将其链接,功能包括链表初始化、节点插入、节点删除、结点查找;文中不讲解链表的原理,如有疑惑先搞清楚链表的实现原理。
链表基础结构
/* 内核链表结构 */
typedef struct LIST_HEAD {
struct LIST_HEAD *prev, *next;
} list_head;
该链表只有双向指针(也可以是单向指针),没有数据域;任何类型的结构体都将其作为成员,这样结果就是所有不同类型的结构体都存在相同的成员,那就是list_head,这样就可以通过list_head间接的链接不同类型结构体
宿主结构体查询
// ptr:表示结构体中list_head的地址
// type:表示结构体类型
// member:表示结构体中的list_head成员
// return:返回结构体的首地址
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member));})
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
这也是本文最重要的一点:container_of(ptr, type, member),就是通过这个宏把list_head的地址转到宿主结构体的首地址,下面这张图展示比较清楚
这里简单举个栗子,代码如下:
typedef struct LCD_DRIVE {
u8 id;
void (*putbuf)(u8 *buf, int len);
list_head list;
u8 type;
} lcd_drive;
假如现在有个结构体指针:lcd_drive *lcd,在已知lcd中list的地址时,如下使用list_entry,也就是container_of得出宿主结构体lcd的地址
lcd_drive *lcd = NULL;
lcd = (lcd_drive *)list_entry(list, typeof(lcd_drive), list);
这里有一个问题,container_of要通过其内部list_head地址得到宿主结构体地址必须知道宿主结构体是什么类型,这在轮询list_head是无法得知的;注意观察lcd_drive这个结构体组成,有一个u8 type,这是判断宿主类型的关键,举个栗子:
u8 list_type = *((char *)list + sizeof(list_head));
当已知在得到lcd_drive结构体的list地址之后,通过将该地址偏移list_head结构体大小得到type的值,以此来判断宿主结构体的类型,(char *)list的作用是保证地址累加单位为1byte;这里有两个限制条件(1:宿主结构体在初始化时要对应给成员type赋值; 2:宿主结构体中type成员要跟在list之后)
如何将不同结构链接起来
前面讲了如何通过宿主结构体中的list和type成员获取宿主结构体首地址;下面讲怎么把不同结构体链接起来,首先初始化链表:
/* 链表头节点 */
list_head *root_list = NULL;
/* 初始化头节点 */
void list_init(list_head **list)
{
*list = (list_head *)malloc(sizeof(list_head));
if (*list) {
/* 头节点首尾指向本身,形成循环链表 */
(*list)->next = *list;
(*list)->prev = *list;
} else {
printf("list init err!\n");
return;
}
}
list_init(&root_list);
其次定义N种不同类型结构体,这里是三种:
/* type one: student */
typedef struct STUDENT {
u8 age;
u16 weight;
int height;
list_head list;
u8 type;
} student;
/* type two: font */
typedef struct FONT {
u8 page;
u16 offset;
u8 *disbuf;
list_head list;
u8 type;
} font;
/* type three: lcd_drive */
typedef struct LCD_DRIVE {
u8 id;
u8 *init_code;
void (*putbuf)(u8 *buf, int len);
list_head list;
u8 type;
} lcd_drive;
根据定义的三种结构体,这里初始化六个变量:
void stu_init(student *stu, u8 age, u16 weight, int height)
{
stu->age = age;
stu->weight = weight;
stu->height = height;
stu->type = TYPE_STUDENT;
}
void font_init(font *font, u8 page, u16 offset, u8 *disbuf)
{
font->page = page;
font->offset = offset;
font->disbuf = disbuf;
font->type = TYPE_FONT;
}
void lcd_init(lcd_drive *lcd, u8 id, u8 *init_code, void *putbuf)
{
lcd->id = id;
lcd->init_code = init_code;
lcd->putbuf = putbuf;
lcd->type = TYPE_LCD_DRIVE;
}
------------------------------------------------------------------------------
u8 *fontbuf0 = "this is fontbuf0!";
u8 *fontbuf1 = "this is fontbuf1!";
u8 init_code0[] = {0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5};
u8 init_code1[] = {0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5};
student *stu0 = (student *)malloc(sizeof(student));
student *stu1 = (student *)malloc(sizeof(student));
font *font0 = (font *)malloc(sizeof(font));
font *font1 = (font *)malloc(sizeof(font));
lcd_drive *lcd0 = (lcd_drive *)malloc(sizeof(lcd_drive));
lcd_drive *lcd1 = (lcd_drive *)malloc(sizeof(lcd_drive));
/* 结构体数据初始化 */
stu_init(stu0, 10, 130, 60);
stu_init(stu1, 13, 150, 80);
font_init(font0, 2, 0x6ff, fontbuf0);
font_init(font1, 5, 0x9ff, fontbuf1);
lcd_init(lcd0, 0, init_code0, putbuf0);
lcd_init(lcd1, 1, init_code1, putbuf1);
然后我们要做的就是将不同宿主结构体中的list成员加入到链表中,这里采用头节点插入,如下:
/* 这里从头节点插入,尾节点插入同理 */
void list_add(list_head *new, list_head *prev, list_head *next)
{
new->prev = prev;
new->next = next;
prev->next = new;
next->prev = new;
}
list_add(&(stu0->list), root_list, root_list->next);
list_add(&(lcd1->list), root_list, root_list->next);
list_add(&(font0->list), root_list, root_list->next);
list_add(&(lcd0->list), root_list, root_list->next);
list_add(&(stu1->list), root_list, root_list->next);
list_add(&(font1->list), root_list, root_list->next);
最后就是循环链表取值
/* 从链表指定位置开始循环, 头节点结束 */
void list_for_each(list_head *list)
{
extern void data_show(list_head *list);
/* 头节点处结束 */
while (list != root_list) {
data_show(list);
/* 反向:list = list->next */
list = list->prev;
}
}
最后
本次主要讲的就是怎么将不同类型结构体通过一条链表来管理,涉及到的关键点就是container_of和怎么判断链表类型的技巧;文中用到的测试代码完整版如下,可直接编译测试,不清楚的地方欢迎留言讨论,空闲时间回复
list_head.c
#include "list_head.h"
/* 链表头节点 */
list_head *root_list = NULL;
/* 初始化头节点 */
void list_init(list_head **list)
{
*list = (list_head *)malloc(sizeof(list_head));
if (*list) {
/* 头节点首尾指向本身,形成循环链表 */
(*list)->next = *list;
(*list)->prev = *list;
} else {
printf("list init err!\n");
return;
}
}
/* 这里从头节点插入,尾节点插入同理 */
void list_add(list_head *new, list_head *prev, list_head *next)
{
new->prev = prev;
new->next = next;
prev->next = new;
next->prev = new;
}
/* 这里从任意节点删除 */
void list_del(list_head *del)
{
del->prev->next = del->next;
del->next->prev = del->prev;
}
/* 从链表指定位置开始循环, 头节点结束 */
void list_for_each(list_head *list)
{
extern void data_show(list_head *list);
/* 头节点处结束 */
while (list != root_list) {
data_show(list);
/* 反向:list = list->next */
list = list->prev;
}
}
/* 释放头节点 */
void list_uninit(list_head *list)
{
free(list);
list = NULL;
}
list_head.h
#ifndef __LIST_HEAD__
#define __LIST_HEAD__
#include "stddef.h"
#include "string.h"
typedef unsigned short u16;
typedef unsigned char u8;
// ptr:表示结构体中list_head的地址
// type:表示结构体类型
// member:表示结构体中的list_head成员
// return:返回结构体的首地址
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member));})
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/* 内核链表结构 */
typedef struct LIST_HEAD {
struct LIST_HEAD *prev, *next;
} list_head;
// 用以区分不同结构体类型
enum {
TYPE_STUDENT,
TYPE_FONT,
TYPE_LCD_DRIVE
};
/* type one: student */
typedef struct STUDENT {
u8 age;
u16 weight;
int height;
list_head list;
u8 type;
} student;
/* type two: font */
typedef struct FONT {
u8 page;
u16 offset;
u8 *disbuf;
list_head list;
u8 type;
} font;
/* type three: lcd_drive */
typedef struct LCD_DRIVE {
u8 id;
u8 *init_code;
void (*putbuf)(u8 *buf, int len);
list_head list;
u8 type;
} lcd_drive;
extern list_head *root_list;
void list_init(list_head **list);
void list_uninit(list_head *list);
void list_add(list_head *new, list_head *prev, list_head *next);
void list_del(list_head *list);
void list_for_each(list_head *list);
#endif
main.c
#include "stdlib.h"
#include "string.h"
#include "list_head.h"
void stu_init(student *stu, u8 age, u16 weight, int height)
{
stu->age = age;
stu->weight = weight;
stu->height = height;
stu->type = TYPE_STUDENT;
}
void font_init(font *font, u8 page, u16 offset, u8 *disbuf)
{
font->page = page;
font->offset = offset;
font->disbuf = disbuf;
font->type = TYPE_FONT;
}
void lcd_init(lcd_drive *lcd, u8 id, u8 *init_code, void *putbuf)
{
lcd->id = id;
lcd->init_code = init_code;
lcd->putbuf = putbuf;
lcd->type = TYPE_LCD_DRIVE;
}
void putbuf0(u8 *buf, int len)
{
printf("lcd0:");
for (int i = 0; i < len; i++) {
printf("%c", buf[i]);
}
printf("\n");
}
void putbuf1(u8 *buf, int len)
{
printf("lcd1:");
for (int i = 0; i < len; i++) {
printf("%c", buf[i]);
}
printf("\n");
}
void data_show(list_head *list)
{
/* 根据list_head地址反推type值,(char *)保证指针以1byte累加 */
u8 list_type = *((char *)list + sizeof(list_head));
printf(">>>>> list addr:0x%x, type:%d\n", list, list_type);
switch (list_type) {
case TYPE_STUDENT:
{
student *stu = NULL;
stu = (student *)list_entry(list, typeof(student), list);
printf("[ age:%d weight:%d height:%d ]\n", \
stu->age, stu->weight, stu->height);
break;
}
case TYPE_FONT:
{
font *fon = NULL;
fon = (font *)list_entry(list, typeof(font), list);
printf("[ page:%d offset:0x%x disbuf:%s ]\n", \
fon->page, fon->offset, fon->disbuf);
break;
}
case TYPE_LCD_DRIVE:
{
lcd_drive *lcd = NULL;
lcd = (lcd_drive *)list_entry(list, typeof(lcd_drive), list);
printf("[ lcd id:%d ]\n", lcd->id);
int i = 0;
printf("init_code:");
while (lcd->init_code[i]) {
printf("0x%x ", lcd->init_code[i]);
i++;
}
printf("\n");
lcd->putbuf((u8 *)"lcd_drive_fun", strlen("lcd_drive_fun"));
break;
}
default:
{
printf("type err!\n");
break;
}
}
}
int main()
{
printf("start test list_head!\n");
u8 *fontbuf0 = "this is fontbuf0!";
u8 *fontbuf1 = "this is fontbuf1!";
u8 init_code0[] = {0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5};
u8 init_code1[] = {0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5};
student *stu0 = (student *)malloc(sizeof(student));
student *stu1 = (student *)malloc(sizeof(student));
font *font0 = (font *)malloc(sizeof(font));
font *font1 = (font *)malloc(sizeof(font));
lcd_drive *lcd0 = (lcd_drive *)malloc(sizeof(lcd_drive));
lcd_drive *lcd1 = (lcd_drive *)malloc(sizeof(lcd_drive));
/* 结构体数据初始化 */
stu_init(stu0, 10, 130, 60);
stu_init(stu1, 13, 150, 80);
font_init(font0, 2, 0x6ff, fontbuf0);
font_init(font1, 5, 0x9ff, fontbuf1);
lcd_init(lcd0, 0, init_code0, putbuf0);
lcd_init(lcd1, 1, init_code1, putbuf1);
list_init(&root_list);
/* 已初始化的所有结构体链表地址 */
printf("addr_list:\n\
root:0x:%x\n\
stu0:0x%x\n\
stu1:0x%x\n\
font0:0x%x\n\
font1:0x%x\n\
lcd0:0x%x\n\
lcd1:0x%x\n\n",\
root_list, \
&(stu0->list), \
&(stu1->list), \
&(font0->list), \
&(font1->list), \
&(lcd0->list), \
&(lcd1->list));
/* 将不同结构体内的list_head插入循环链表,首部插入 */
list_add(&(stu0->list), root_list, root_list->next);
list_add(&(lcd1->list), root_list, root_list->next);
list_add(&(font0->list), root_list, root_list->next);
list_add(&(lcd0->list), root_list, root_list->next);
list_add(&(stu1->list), root_list, root_list->next);
list_add(&(font1->list), root_list, root_list->next);
list_del(&(lcd1->list));
/* list_del(&(stu0->list)); */
/* 可从已知的任意节点开始查询 */
list_for_each(&(stu0->list));
list_uninit(root_list);
printf("finish test list_head!\n");
return 0;
}
Makefile
exe: list_head.c main.c
gcc list_head.c main.c -o exe