Linux 内核中常用的两个宏定义
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
#endif
#ifndef container_of
#define container_of(ptr, type, member) ({ \
const typeof(((type*)0)->member)* __mptr = (ptr); \
(type*)((char*)__mptr - offsetof(type, member));})
#endif
见招拆招 - 第一式:编译器做了什么?
offsetof 用于计算 TYPE 结构体中 MEMBER 成员的偏移位置
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
#endif
- 编译器清楚的知道结构体成员的偏移位置
- 通过结构体变量首地址与偏移量定位成员变量
编程实验: offsetof 原理剖析
#include <stdio.h>
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
#endif
struct ST
{
int i; // 0
int j; // 4
char c; // 8
};
void func(struct ST *pst)
{
int *pi = &(pst->i); // (unsigned int)pst + 0
int *pj = &(pst->j); // (unsigned int)pst + 4
char *pc = &(pst->c); // (unsigned int)pst + 8
printf("pst = %p\n", pst);
printf("pi = %p\n", pi);
printf("pj = %p\n", pj);
printf("pc = %p\n", pc);
}
int main()
{
struct ST s = {0};
func(&s);
printf("---------\n");
func(NULL);
printf("---------\n");
printf("offset i : %d\n", offsetof(struct ST, i));
printf("offset j : %d\n", offsetof(struct ST, j));
printf("offset c : %d\n", offsetof(struct ST, c));
return 0;
}
结论
计算成员变量与其结构体变量首地址的偏移量
见招拆招 - 第二式:({})
- ({}) 是 GNU 编译器的语法扩展
- ({}) 与逗号表达时类似,结果为最后一个语句的值
#include <stdio.h>
void method_1()
{
int a = 0;
int b = 0;
int r = (
a = 1,
b = 2,
a + b
);
printf("r = %d\n", r);
}
void method_2()
{
int r = ({
int a = 1;
int b = 2;
a + b;
});
printf("r = %d\n", r);
}
int main()
{
method_1();
method_2();
return 0;
}
输出:
r = 3
r = 3
见招拆招- 第三式: typeof 是一个关键字吗?
- typeof 是 GUN C 编译器的特有关键字
- typeof 只在编译期生效,用于得到变量的类型
#include <stdio.h>
int main()
{
int i = 100;
typeof(i) j = i;
const typeof(j) *p = &j;
printf("sizeof(j) = %d\n", sizeof(j));
printf("j = %d\n", j);
printf("*p = %d\n", *p);
return 0;
}
输出:
sizeof(j) = 4
j = 100
*p = 100
见招拆招 - 第四式:最后的原理
数学关系:
pc = p + offset
==>
size_t offset = offsetof(struct ST, c);
struct ST *p = (struct ST*)((char*)pc - offset);
编程实验:container_of 原理剖析
#include <stdio.h>
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
#endif
#ifndef container_of
#define container_of(ptr, type, member) ({ \
const typeof(((type*)0)->member)* __mptr = (ptr); \
(type*)((char*)__mptr - offsetof(type, member));})
#endif
struct ST
{
int i;
int j;
char c;
};
int main()
{
struct ST s = {0};
char *pc = &s.c;
struct ST *pst = container_of(pc, struct ST, c);
printf("&s = %p\n", &s);
printf("pst = %p\n", pst);
return 0;
}
输出:
&s = 0061FEB8
pst = 0061FEB8
结论
根据成员变量地址推导结构体变量首地址
思考:
const typeof(((type*)0)->member)* __mptr = (ptr);
下面这一段代码测试证明没有这一行也可以得到正确的结果,那它到底有什么作用呢?
#include <stdio.h>
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
#endif
#ifndef container_of
#define container_of(ptr, type, member) ({ \
const typeof(((type*)0)->member)* __mptr = (ptr); \
(type*)((char*)__mptr - offsetof(type, member));})
#endif
#ifndef container_of_new
#define container_of_new(ptr, type, member) ((type*)((char*)ptr - offsetof(type, member)))
#endif
struct ST
{
int i;
int j;
char c;
};
int main()
{
struct ST s = {0};
char *pc = &s.c;
struct ST *pst = container_of_new(pc, struct ST, c); // 注意!!!
printf("&s = %p\n", &s);
printf("pst = %p\n", pst);
return 0;
}
输出:
&s = 0061FEBC
pst = 0061FEBC
答疑 1:为了能够提供微弱但很重要的类型安全检查
因为 continer_of 只能用宏来实现,但宏是由预处理器处理,仅进行简单的文本替换,不会进行任何的类型检查。这就有可能导致在编写代码时,由于疏忽传递了错误的类型指针而编译器不发出任何警告。
测试:
int main()
{
struct ST s = {0};
int e = 0;
int *pe = &e;
struct ST *pst = container_of_new(pe, struct ST, c);
printf("&s = %p\n", &s);
printf("pst = %p\n", pst);
return 0;
}
输出:【编译无错误,无警告,但运行结果错误】
&s = 0061FEBC
pst = 0061FEB0
int main()
{
struct ST s = {0};
int e = 0;
int *pe = &e;
struct ST *pst = container_of(pe, struct ST, c);
printf("&s = %p\n", &s);
printf("pst = %p\n", pst);
return 0;
}
编译输出:
warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
const typeof(((type*)0)->member)* __mptr = (ptr); \
^
答疑 2: 为什么使用 ({})
当定义指针变量时,一行代码再也无法完成 continer_of 的整体功能(逗号表达式中不可以定义变量),于是使用了 GNU 的扩展语法({})分多行定义局部变量。
答疑 3: 此行代码是怎样获取成员变量的类型的呢?
typeof(((type*)0)->member)
小结
- 编译器清楚的知道结构体成员变量的偏移位置
- ({}) 与逗号表达式类型,结果为最后一个语句的值
- typeof 只在编译期生效,用于得到变量的类型
- container_of 使用 ({}) 进行类型安全检查
以上内容整理于狄泰软件学院系列课程,请大家保护原创!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。