container_of用法及实现

一、简介

`container_of`是Linux内核中一个非常重要的宏,其作用是通过一个结构体某个成员的地址来获取整个结构体的地址。所以,掌握`container_of`的用法和实现原理不仅可以更好地理解Linux内核的实现,还可以提高代码的可读性和可维护性。

二、用法

`container_of`的定义如下:

```c

#define container_of(ptr, type, member) ({ \

const typeof( ((type *)0)->member ) *__mptr = (ptr); \

(type *)( (char *)__mptr - offsetof(type, member) );})

```

该宏接收三个参数,分别为:

- `ptr`:表示某个结构体成员的指针。

- `type`:表示结构体的类型。

- `member`:表示结构体中的某个成员名称。

使用`container_of`时,首先需要传入一个成员指针`ptr`,然后根据成员名称`member`获取该成员在结构体中的偏移量,并通过`offsetof`宏计算出整个结构体的地址,最后强制转换为`type *`类型并返回。

例如下面的代码:

```c

struct student {

char name[20];

int age;

};

struct student *p = (struct student *)0x1234;

char *name = p->name;

struct student *ps = container_of(name, struct student, name);

```

这里通过`name`成员指针获取整个结构体的指针。由于`name`成员在结构体中的偏移量为0,所以使用`offsetof`计算出整个结构体的地址恰好为`0x1234`,最后强制转换为`struct student *`类型并返回。

三、实现

`container_of`的实现主要涉及两个关键点:结构体成员在结构体中的偏移量及`offsetof`的底层实现。

1. 结构体成员在结构体中的偏移量

为了计算出结构体成员在结构体中的偏移量,可以使用以下两种方式:

- 直接打印出成员的地址,通过手动计算偏移量来确定。

```c

struct test {

int num;

char ch;

float f;

}t;

printf("%p\n", &(t.num)); //输出0x7fff21653680

printf("%p\n", &(t.ch)); //输出0x7fff21653684

printf("%p\n", &(t.f)); //输出0x7fff21653688

```

由于每个成员在内存中的地址是连续的,所以可以通过计算相邻两个成员地址的差值来得到偏移量。例如,`&(t.ch)`和`&(t.num)`的差值为4,因此`ch`在`num`后面,所以偏移量为4。

- 使用`offsetof`宏

`offsetof`宏的定义如下:

```c

#define offsetof(type, member) __builtin_offsetof(type, member)

```

该宏的实现是依赖与编译器的,可以使用编译器自带的`__builtin_offsetof`函数实现,也可以使用其他方式实现。但无论如何,`offsetof`的本质都是通过某个成员在结构体中的地址减去结构体地址来得到该成员在结构体中的偏移量。

2. `offsetof`的底层实现

在实现`offsetof`时,主要使用了一些C语言的特性:

- 使用一个匿名的结构体来表示需要计算偏移量的结构体。

- 使用该结构体中某个成员的地址减去该结构体的地址(即该成员在结构体中的偏移量)来得到偏移量。

例如,以下代码中使用了一个匿名的结构体来计算`struct test`中`num`成员的偏移量:

```c

struct test {

int num;

char ch;

float f;

} t;

int offset = (int)&(((struct test *)0)->num);

printf("%d", offset);

```

该代码实现的思路是将`0`强制转换为`struct test *`类型,并通过结构体成员的地址来计算出结构体成员在结构体中的偏移量。

因为结构体成员地址减去结构体地址得到的偏移量本身就是一个无符号整型,所以可以直接将其强制转换为`int`类型并输出。最终输出的结果为`0`,等价于使用`offsetof`宏获取`num`的偏移量。

四、案例说明

以下是一个使用`container_of`的示例代码:

```c

#include

#include

struct student {

char name[20];

int age;

};

#define container_of(ptr, type, member) ({ \

const typeof( ((type *)0)->member ) *__mptr = (ptr); \

(type *)( (char *)__mptr - offsetof(type, member) );})

int main() {

struct student stu = {"Tom", 18};

char *p = stu.name;

struct student *pst = container_of(p, struct student, name);

printf("name: %s, age: %d\n", pst->name, pst->age);

return 0;

}

```

该代码定义了一个`student`结构体,有两个成员`name`和`age`。使用`container_of`宏来获取`name`成员的偏移量和`student`结构体的地址,并计算出`student`结构体的指针,最后输出`name`和`age`的值。

假设内存中`stu`结构体的地址为`0x1000`,`name`成员的地址为`0x1000`,`age`成员的地址为`0x100c`。根据以上偏移量计算方法,`name`成员的偏移量为0,`age`成员的偏移量为4,所以可以得到`container_of`的实现:

```c

struct student *container_of(char *ptr) {

struct student *pst = (struct student *)(ptr - 0);

return pst;

}

```

根据以上偏移量计算方法,`name`成员的地址减去偏移量等于`student`结构体的地址,即:

```c

&(stu.name) - 0 = &stu = 0x1000

```

因此,使用`container_of(p, struct student, name)`传入`ptr`为`&(stu.name)`,`type`为`struct student`,`member`为`name`,就可以得出最终的结果:

```c

struct student *pst = (struct student *)(&(stu.name) - 0);

```

最后输出的结果为:

```

name: Tom, age: 18

```

总结

`container_of`是Linux内核中非常重要的一个宏,其作用是通过一个结构体某个成员的地址来获取整个结构体的地址。实现`container_of`思路是通过结构体成员地址减去结构体地址得到结构体成员的偏移量,然后再用结构体成员地址减去偏移量得到结构体地址。因为`container_of`的实现依赖于`offsetof`宏,因此也需要了解`offsetof`的底层实现。掌握了`container_of`的用法和实现原理,不仅可以更好地理解Linux内核的实现,还可以提高代码的可读性和可维护性。 如果你喜欢我们三七知识分享网站的文章, 欢迎您分享或收藏知识分享网站文章 欢迎您到我们的网站逛逛喔!https://www.37seo.cn/

点赞(98) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
发表
评论
返回
顶部