分析 container_of 宏

分析 container_of 宏

功能和使用

这个宏的作用是通过得知结构体中的某个成员变量的地址,然后找到结构体的首地址。

举例说明:

有个结构体:

struct person {
    int age;
    int salary;
    char *name;
};

struct person leo;
int *salary_ptr = &(leon.salary);

得知这个结构体salary的指针:salary_ptr,如何能找到leo这个结构体变量的首地址。找到首地址之后,你可以通过这个首地址再去找该结构体中其他的变量

先想一下,你来设计,你应该如何找到:直观感觉就是往回数,结构体在存储的时候,是顺序存储的,找到salary是第几个变量,然后减去之前的变量占用的空间,就是结构体的首地址了。还要考虑对齐的问题。暂时先把这个思路留在这里。继续往下看

简单写个程序测试一下

Makefile文件

obj-m += container_of.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

测试代码:container_of.c

#include <linux/module.h>
#include <linux/kernel.h>

struct person {
    int age;
    int salary;
    char *name;
};

int container_of_init(void)
{
    struct person leo;
    struct person *leo_ptr;

    int *age_ptr = &(leo.age);
    int *salary_ptr = &(leo.salary);
    char **name_ptr = &(leo.name);

    leo_ptr = container_of(age_ptr, struct person, age);
    printk(KERN_INFO "addr of person:0x%p\n", leo_ptr);

    leo_ptr = container_of(salary_ptr, struct person, salary);
    printk(KERN_INFO "addr of person:0x%p\n", leo_ptr);

    leo_ptr = container_of(name_ptr, struct person, *name);
    printk(KERN_INFO "addr of person:0x%p\n", leo_ptr);

    printk(KERN_INFO "addr of age:0x%p, 0x%p\n", &(leo_ptr->age), &(leo.age));
    printk(KERN_INFO "addr of salary:0x%p, 0x%p\n", &(leo_ptr->salary), &(leo.salary));
    printk(KERN_INFO "addr of name:0x%p, 0x%p\n", leo_ptr->name, leo.name);

    return 0;
}

void container_of_exit(void)
{
    return;
}
module_init(container_of_init);
module_exit(container_of_exit);

输出了结果:通过成员变量,都找到了结构体的首地址,并且再通过结构体的首地址,找到了变量的地址。

[158680.247539] addr of person:0x00000000e491801b
[158680.247541] addr of person:0x00000000e491801b
[158680.247542] addr of person:0x00000000e491801b
[158680.247542] addr of age:0x00000000e491801b, 0x00000000e491801b
[158680.247549] addr of salary:0x000000001e76c839, 0x000000001e76c839
[158680.247550] addr of name:0x0000000063561914, 0x0000000063561914

代码分析

container_of宏位于<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) ({				\
	void *__mptr = (void *)(ptr);					\
	BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&	\
			 !__same_type(*(ptr), void),			\
			 "pointer type mismatch in container_of()");	\
	((type *)(__mptr - offsetof(type, member))); })

慢慢来理解这个宏,这个宏由两部分组成,一个是BUILD_BUG_ON_MSG,还有就是首先就是((type *)(__mptr - offsetof(type, member)));

先看BUILD_BUG_ON_MSG,编译告警输出消息。

/**
 * BUILD_BUG_ON_MSG - break compile if a condition is true & emit supplied
 *		      error message.
 * @condition: the condition which the compiler should know is false.
 *
 * See BUILD_BUG_ON for description.
 */
#define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)

是在编译阶段输出告警,举个例子:

正确的指针类型定义:
char **name_ptr = &(leo.name);
错误的指针类型
int **name_ptr = &(leo.name);

由于用了错误的指针,会在编译阶段输出:

./include/linux/compiler.h:324:38: error: call to ‘__compiletime_assert_25’
 declared with attribute error: pointer type mismatch in container_of()

接着继续展开((type *)(__mptr - offsetof(type, member)));

void *__mptr = (void *)(ptr);
((type *)(__mptr - offsetof(type, member))); 

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

将例子中的参数带入

struct person {
    int age;
    int salary;
    char *name;
};
struct person leo;
int *salary_ptr = &(leo.salary);
leo_ptr = container_of(salary_ptr, struct person, salary);
展开后:
(struct person*)((void *)(salary_ptr) - ((size_t))&((struct person*)0->salary));

第1部分

(void *)(salary_ptr):这部分是结构体中salary的位置,用salary_ptr表示,将salary_ptr指针转换为void指针,这个很好理解。

第2部分

((size_t))&((struct person*)0->salary)):这部分就是结构体首地址到salary的偏移量。

由于只需要一个偏移量,那将结构体的首地址设置成从0开始,(struct person*)0,就构造了这个结构体,但是地址是0,但是这个结构体构造还是不变的,(struct person*)0->salary表示了结构体首部到salary的偏移量。

这样取出来是结构体的成员,还要对这个成员进行取地址运算&,得到成员的地址。将这个地址转换为(size_t)类型,这样就表示了结构体首部到salary的偏移。

计算偏移量

用第1部分减去第2部分,就得到了结构体的首地址,最后将该地址,转换成(struct person*)返回。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值