版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xi_xix_i/article/details/134625972
1. 开发环境
linux 4.19
2. container_of用法简介
container_of
是一个宏定义,可以根据传入的结构体中某一变量的地址,返回该变量所在的结构体。其源码位于include/linux/kernel.h中,在Linux内核中的定义如下(可能有不同,我看过4.1版本的内核是没有警告语句的):
/**
* 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))); })
传入三个参数:
- ptr,要返回的结构体中的某一变量。
- type,要返回的结构体的类型。
- member,该变量在结构体中的命名。
3. container_of详解
首先要知道,({})
是GNU C编译器的语法扩展,其结果为最后一个语句的值,实验如下:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int a = 1, b =1;
int c =({
a = 2;
b = 3;
b;
});
printf("a:%d, b:%d, c:%d\n", a, b, c);
return 0;
}
结果如下图所示:
实验2:
int main(void)
{
int a = 1, b =1;
int c =({
a = 2;
b = 3;
int d = 5;
a + d;
});
printf("a:%d, b:%d, c:%d\n", a, b, c);
return 0;
}
结果如下:
同时{}
也起到了限制变量作用域的效果,在{}
内定义的变量的作用域只能在{}
内,如果在作用域外使用则会报错:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
int a = 1, b =1;
int c =({
a = 2;
b = 3;
int d = 5;
d;
});
printf("a:%d, b:%d, c:%d d:%d\n", a, b, c, d);
return 0;
}
结果如下所示:
所以container_of(ptr, type, member)
最后的返回值其实是:((type *)(__mptr - offsetof(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)))
第一个语句是定义了一个名为__mptr
的void*
类型指针,其值等于传入的结构体中的某一变量的地址。第二个语句是对传入的参数做检查,先不考虑这个。最后一个语句中调用了另外一个宏:offsetof
,该宏定义位于include/linux/stddef.h中,该宏牵扯到的另外一个宏__compiler_offsetof
定义在include/linux/compiler_typys.h中:
include/linux/stddef.h:
#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE, MEMBER) __compiler_offsetof(TYPE, MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#endif
include/linux/compiler_typys.h:
#define __compiler_offsetof(a, b) __builtin_offsetof(a, b)
可以看到,因为定义了__compiler_offsetof
宏,所以offsetof(type, member)
展开后即为__builtin_offsetof(type, member)
。
在这篇文章中提到,这些__builtin_开头的符号其实是一些编译器内置的函数或者编译优化处理开关等,其作用类似于宏。宏是高级语言用于预编译时进行替换的源代码块,而内置函数则是用于在编译阶段进行替换的机器指令块。因此编译器的这些内置函数其实并不是真实的函数,而只是一段指令块,起到编译时的内联功能。
而这里的__builtin_offsetof
的作用,其实就与没有定义__compiler_offsetof
宏时的offsetof(TYPE, MEMBER)
的表达式((size_t)&((TYPE *)0)->MEMBER)
的作用是一样的。当取结构体中某一变量的地址时,获取到的是该变量的绝对地址。因为结构体的内存空间是连续的,所以如果知道该变量在结构体内的偏移值,则可知道结构体的首地址,即可得到该结构体。
那么如何来获取该变量在结构体内的偏移值呢?就是通过&((TYPE *)0)->MEMBER)
来获取。若结构体的首地址为0的话,那么获取的变量地址既是绝对地址,也是在结构体内的偏移值。
然后根据我们传入的该变量的绝对地址__mptr
,减去所获取的偏移值,即可得到该变量所在结构体的首地址,然后返回即可。
4. container_of测试
编写如下代码进行测试,因为有些__builtin_开头的一些编译器内置的函数无法使用,所以我对其进行了修改和替换,但是核心代码没有变化。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
// #define __compiler_offsetof(a, b) __builtin_offsetof(a, b)
// #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
// #define compiletime_assert(condition, msg) \
// _compiletime_assert(condition, msg, __compiletime_assert_, __LINE__)
#ifdef __compiler_offsetof
#define offsetof(TYPE, MEMBER) __compiler_offsetof(TYPE, MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#endif
#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))); })
typedef struct test_s{
int member_a;
char member_b;
int member_c;
}test_s;
int main(void)
{
test_s test = {
.member_a = 1,
.member_b = 'b',
.member_c = 3,
};
test_s* test_pointer = container_of(&test.member_b, test_s, member_b);
printf("test.member_b address:0x%x, offsetof(test_s, member_b):0x%x\n", &(test.member_b), offsetof(test_s, member_b));
printf("test address:0x%x, test_pointer:0x%x\n", &test, test_pointer);
printf("test_pointer->member_c:%d\n", test_pointer->member_c);
return 0;
}
测试结果如下:
通过加减法运算可以看到test.member_b
的地址0xd8a0减offset
即0x4(通过理论分析也可知,member_b
的offset是经过一个int类型的member_a
,一个int类型恰好为0x4)恰好等于0xd89c,正如前面分析的逻辑一样。通过理论分析也可以知道,最后也成功打印出了test
中member_c
的值。