linux内核list分析一:前言

链表list是linux内核最经典的数据结构之一,不过在深入学习链表的实现之前,需要了解几个知识:offsetof、typeof、container_of

1、offsetof

offsetof的作用是返回结构体中的某个成员在该结构体中的偏移量,请看下面的例子:

struct person
{
int height;
int weight;
};

printf("%u\n", offsetof(struct person, height)); // 0
printf("%u\n", offsetof(struct person, weight)); // 4

而offsetof的真实面目是

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

这里巧妙的利用了0地址。也许有人会产生疑问,怎么可能会对0地址进行操作呢?为了更容易理解,下面都使用struct person为例。其实,0是一个具体的常量值,它是一个地址,而(struct person *)0则是一个指针,而且是一个指针常量(即指针本身是常量,但它指向的地址里的内容可以改变)。只要我们不去对一个空指针进行读写,就不会存在非法访问内存的问题,例如:

//  读取操作
struct person *xiao_hua = 0;
struct person tmp = *xiao_hua; // error

// 写入操作
struct person *xiao_hua = 0;
struct person tmp = {180, 60};
*xiaohua = tmp; // error

那么,其他的操作都是OK的。

offsetof(struct person, weight) 经过宏替换后变为 ((size_t) &((struct person )0)->weight)
1、(struct person
)0 表示 一个指向struct person结构的指针,虽然这个struct person结构不存在,但只要不去对它进行读写就OK
2、(struct person )0)->weight 表示 1中的指针所指向的那个struct person结构的weight成员
3、&((struct person
)0)->weight 表示 1中的指针所指向的那个struct person结构的weight成员的地址
4、weight成员的地址 减去 它所在的struct person结构的地址,就可以得出weight在struct person结构中的偏移量,但是此时,struct person结构的地址为0,所以weight成员的地址就是weight在struct person结构中的偏移量

2、typeof

typeof关键字是C语言中的一个新扩展,这个特性在linux内核中应用非常广泛。
typeof的参数可以是两种形式:表达式或类型。

(1) 表达式的例子:

//  以下示例声明了int类型的var变量,因为表达式foo()是int类型的。由于表达式不会被执行,所以不会调用foo函数。
extern int foo();
typeof(foo()) var; // 等价于 int var;

(2) 类型的例子:

typeof(int *) a,b; // 等价于 int *a,*b;

3、container_of

它的作用是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针。

#define container_of(ptr, type, member) ({                      \
const typeof ( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( ( char *)__mptr - offsetof(type,member) );})


struct person xiao_hua = {180, 60};
struct person *p_xiao_hua = container_of(&xiao_hua.weight, struct person, weight);
printf("%d\n", p_xiao_hua->height); // 180

container_of(&xiao_hua.weight, struct person, weight) 宏替换后的结果为

const typeof ( ((struct person *)0)->weight ) *__mptr = (&xiao_hua.weight);
(struct person *)( ( char *)__mptr - offsetof(struct person,weight) );})

第一个语句中的‘typeof ( ((struct person )0)->weight )’ 其实就是 ‘int’,所以第一个语句就是’const int __mptr = (&xiao_hua.weightr)’
第二个语句将weight的地址减去weight在struct person中的偏移量就得到了struct person结构变量的地址。

这里使用了一个中间变量__mptr,也许我们会质疑,它是多余的。但是,请看下面的情况

#define container_of(ptr, type, member) ({                      \
(type *)( ( char *)ptr - offsetof(type,member) );})


struct person xiao_hua = {180, 60};
struct person *p_xiao_hua = container_of(&xiao_hua, struct person, weight); // 错误使用container_of
printf("%d\n", p_xiao_hua->height);

所以中间变量__mptr这里起到了提醒开发者的功能。如果开发者传入的ptr指针指向的类型,与结构体中成员的类型不符,编译器在这里会打印一条warning,提示开发者可能存在的错误。