最近发现两个非常牛的宏:
一个是计算某个结构体成员的偏移量,
一个是计算一个结构体的首地址
这两个宏在Linux kernel里非常基础,非常常用,
今天抽闲暇时间好好调试了一番。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
* 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) ({ \
const typeof(((type *)0)->member)*__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
其中,typeof的使用类似于sizeof的使用,
typeof返回一个数据类型,sizeof返回一个数据结构的大小(byte),
gcc version
gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)
1、宏offsetof的例子(来自man offsetof)
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
struct s {
int i;
char c;
double d;
char a[];
};
/* Output is compiler dependent */
printf("offsets: i=%ld; c=%ld; d=%ld a=%ld\n",
(long) offsetof(struct s, i),
(long) offsetof(struct s, c),
(long) offsetof(struct s, d),
(long) offsetof(struct s, a));
printf("sizeof(struct s)=%ld\n", (long) sizeof(struct s));
exit(EXIT_SUCCESS);
}
//output
offsets: i=0; c=4; d=8 a=16
sizeof(struct s)=16
【分析】
- 该宏中字段TYPE为结构体类型,MEMBER为结构体内的变量名。
- (TYPE *)0) 是欺骗编译器说有一个指向结构TYPE 的指针,其地址值0 。
- (TYPE *)0)->MEMBER 是获得结构体TYPE中成员变量MEMBER的地址. 因为基址为0,所以,这时MEMBER的地址当然就是MEMBER在TYPE中的偏移了。
- 同时也考虑了字节对齐。
2、宏container_of的例子
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
int main(void)
{
struct s {
int i;
char c;
double d;
char a[];
};
struct s m_s, *p_m_s;
double *p_d = &m_s.d;
printf("0x%.8x\n",&m_s);
printf(" 0x%.8x \n 0x%.8x \n 0x%.8x \n 0x%.8x \n",&m_s.i,&m_s.c,&m_s.d,m_s.a);
//p_m_s = container_of(p_d, struct s, d);
printf("--0x%x\n",container_of(p_d, struct s, d));
exit(EXIT_SUCCESS);
}
output
0xb39e8290
0xb39e8290
0xb39e8294
0xb39e8298
0xb39e82a0
--0xb39e8290
其中有三个地址是一样的,也就是结构体的首地址。
【分析】
- typeof( ( (type *)0)->member )为取出member成员的变量类型,
- 定义__mptr指针ptr为指向该成员变量的指针(即指向ptr所指向的变量处),
- (char *)__mptr - offsetof(type,member)), 用该成员变量的实际地址减去该变量在结构体中的偏移,来求出结构体起始地址,
- 在这个例子里如果不添加宏container_of,有可能编译不过,原因:或许typeof是库自带,或许是我的编译路径不全,或许其它^_^。
另外,最近老在想sizeof到底是如果实现的?