本文参考《Linux操作系统原理与应用》以及如下博客:
https://blog.csdn.net/this_capslock/article/details/39189947
给定一个结构体定义type,这个结构体中某个成员变量的名字member以及它的地址ptr,如何得到包含此成员变量的结构体的地址?即通过结构体成员的地址获取结构体变量的地址?
为了便于分析,我们给出一个实例来说明
struct father_t {
int a;
char *b;
double c;
}f;
根据C语言对struct类型的存储特性,我们可以画这么一个图示:
通过分析图示,我们可以看出,我们只需要把当前知道的成员变量的地址ptr,减去它在结构体当中相对偏移4就的到了结构体的地址(ptr-4)。
一、list_entry宏分析
#define list_entry(ptr, type, member) \
((type *)( (char *)(ptr) - (unsigned long)(&((type*)0)->member)))
如下图,指针ptr指向结构体type中的成员member;通过指针ptr,返回结构体type的起始地址,也就是list_entry返回指向type类型的指针。
分析:
((type *)0)->member
把0地址转换为type结构的指针,然后获取该结构中member域的指针,也就是获得了member在type结构中的偏移量(char *)(ptr)
求出的是ptr的绝对地址- 二者相减,于是得到type类型结构体的起始地址
二、typeof学习
typeof关键字是C语言中的一个新扩展,这个特性在linux内核中应用非常广泛。
typeof 是自动推导后面 ( ) 里的数据类型,所以 typeof(int *) 直接推导出了 int * 型
这样的话,当遇到一个非常复杂的表达式我们很难推断其类型的时候,typeof 就很有用了。另外有一点要注意:typeof 是 GNU C 标准里特有的扩展,标准的 ISO C 并没有这个关键字,所以在编译的时候不能加任何 ISO 的 C 标准选项( -std=c90 ),否则会报错,此时把 -std=c90 改成 -std=gnu90 即 GNU 的标准即可。
三、代码
首先看看Linus的实现:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
* container_of - 通过结构体的一个成员获取容器结构体的指针
* @ptr: 指向成员的指针。
* @type: 成员所嵌入的容器结构体类型。
* @member: 结构体中的成员名。
*/
#define container_of(ptr, type, member) ({
const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type *)( (char *)__mptr - offsetof(type,member) );
})
(1)定义一个中间变量__mptr,它等于提供给宏的参数ptr,也就是指向某个成员的指针。这个中间变量的命名意义是:
“__”代表内部使用,内核编程中常常这么做;“m”代表middle。
为了避免对 ptr及prt指向的内容造成破坏,这里不直接使用 ptr 而要多多加一个__mptr。
(2)把__mptr转换成 char *类型,因为offsetof得到的偏移量是以字节为单位。两者相减得到结构体的起始位置,再强制转 换成type类型。
这个宏的作用,就是通过一个容器(结构体)中某个成员的指针得到指向这个容器(结构体)的指针,简单的说就是通过成员 找容器。
这里代码进行验证:
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#define list_entry(ptr, type, member) \
((type *)( (char *)(ptr) - (unsigned long)(&((type*)0)->member)))
#define offsetof(type, member) ((size_t) &((type *)0)->member)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) ); \
})
struct father_t {
int a;
char *b;
double c;
}f;
int main()
{
char *ptr = (char *)&(f.b);
printf("stndard entry address:0x%x\n",&f);
printf("stndard member address:0x%x\n",&(f.b)); //
printf("stndard member address:0x%x\n",ptr);
printf("offset: %d\n",offsetof(struct father_t,b));
printf("my entry :0x%x\n",list_entry(ptr,struct father_t,b));
printf("linus entry :0x%x\n",container_of(ptr,struct father_t,b));
return 0;
}
这里首先使用了自己编写的list_entry,之后使用了Linux源代码中的container_of,只是Linus的代码在有些gcc环境下编译不通过,在本地环境下测试通过。