typeof, offsetof 和container_of
要理解Linux中实现的双向循环链表("侵入式"链表),首先得弄明白宏container_of。 本文尝试从gcc的关键字typeof和宏offsetof入手,循序渐进地剖析宏container_of之实现原理。
1. typeof (from: https://en.wikipedia.org/wiki/Typeof)
typeof is an operator provided by several programming languages to
determine the data type of a variable. This is useful when
constructing programs that must accept multiple types of data
without explicitly specifying the type. The GNU compiler (GCC) extensions for the C programming language provide
typeof: #define max(a, b) \
({ typeof (a) _a = (a); \
typeof (b) _b = (b); \
_a > _b ? _a : _b; })
typeof和sizeof一样,都是关键字。只不过typeof不是标准的c语言关键字,而是gcc支持的(扩展)关键字。 typeof的作用是取得某个变量的数据类型。例如:
unsigned int a = ; // typeof (a) is unsigned int
short b = ; // typeof (b) is short
2. offsetof (from: include/linux/stddef.h)
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
宏offsetof的作用是获取某个成员变量(MEMBER)在其所在的结构体(TYPE)里的偏移。 上面的宏实现得非常巧妙(如果你正好也熟悉汇编,一定会英雄所见略同),剖析如下:
() P = (TYPE *) // 将地址0x0强制转化为类型为TYPE的结构体X的首地址
() M = P->MEMBER // 访问X的成员变量MEMBER
() A = &M // 取得X的成员变量MEMBER的内存地址
() O = (size_t)A // 将成员变量MEMBER的内存地址强制转换成偏移量(Offset),
= (size_t)&M // 由于内存地址也是无符号整数,所以MEMBER相对于结构体X首地址的偏移Offset等于&M
= (size_t)&(P->MEMBER) = (size_t)&P->MEMBER // 注意: -> 比 & 优先级高
= (size_t)&(P)->MEMBER = (size_t)&((TYPE *))->MEMBER
为了理解更容易一些,不妨(多加几重括号)将宏offsetof定义为:
#define offsetof(TYPE, MEMBER) ((size_t)(&(((TYPE *)0)->MEMBER)))
进而,用图解析如下: (此图为本人原创,如需转载请注明出处)

3. container_of (from: include/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) ({ \
const typeof( ((type *))->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
- L9: 用一个临时变量__mptr保存成员变量的指针ptr
- L10: offsetof(type, member): 计算出成员变量相对于其所在的结构体的偏移,不妨记为OFFSET
- L10: (char *)__mptr - OFFSET, 就是成员变量所在的结构体的首地址 (注意: 对(char *)巧妙的应用)
因此, 宏container_of的作用就是根据某个成员变量的内存地址,反推出其所在的结构体变量的首地址。用图解析如下: (此图为本人原创,如需转载请注明出处)

示例代码: foo.c
#include <stdio.h>
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *))->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
typedef struct foo_s {
int m_int;
short m_short;
char m_char;
long long m_longlong;
} foo_t;
int
main(int argc, char *argv[])
{
foo_t ox = {0x12345678, 0x1234, 'A', 0xfedcba9876543210};
char *p3 = &ox.m_char;
foo_t *p = container_of(p3, foo_t, m_char);
printf("foo_t ox (%p) sizeof(foo_t) = %d\n", &ox, sizeof (foo_t));
printf("foo_t *p (%p) p3(%p)\n", p, p3);
printf("foo_t->m_int = %#x\t\t(%d)(%p)\n",
p->m_int, offsetof(foo_t, m_int), &ox.m_int);
printf("foo_t->m_short = %#x\t\t(%d)(%p)\n",
p->m_short, offsetof(foo_t, m_short), &ox.m_short);
printf("foo_t->m_char = %c\t\t\t(%d)(%p)\n",
p->m_char, offsetof(foo_t, m_char), &ox.m_char);
printf("foo_t->m_longlong = %#llx\t(%d)(%p)\n",
p->m_longlong, offsetof(foo_t, m_longlong), &ox.m_longlong);
return ;
}
编译并运行
$ gcc -g -Wall -m32 -o foo foo.c
$ ./foo
foo_t ox (0xbfdfe990) sizeof(foo_t) =
foo_t *p (0xbfdfe990) p3(0xbfdfe996)
foo_t->m_int = 0x12345678 ()(0xbfdfe990)
foo_t->m_short = 0x1234 ()(0xbfdfe994)
foo_t->m_char = A ()(0xbfdfe996)
foo_t->m_longlong = 0xfedcba9876543210 ()(0xbfdfe998)
反汇编并结合gcc -E foo.c
(gdb) set disassembly-flavor intel
(gdb) disas /m main
Dump of assembler code for function main:
{
0x0804841d <+>: push ebp
0x0804841e <+>: mov ebp,esp
0x08048420 <+>: and esp,0xfffffff0
0x08048423 <+>: sub esp,0x40 foo_t ox = {0x12345678, 0x1234, 'A', 0xfedcba9876543210};
0x08048426 <+>: mov DWORD PTR [esp+0x30],0x12345678
0x0804842e <+>: mov WORD PTR [esp+0x34],0x1234
0x08048435 <+>: mov BYTE PTR [esp+0x36],0x41
0x0804843a <+>: mov DWORD PTR [esp+0x38],0x76543210
0x08048442 <+>: mov DWORD PTR [esp+0x3c],0xfedcba98 char *p3 = &ox.m_char;
0x0804844a <+>: lea eax,[esp+0x30]
0x0804844e <+>: add eax,0x6
0x08048451 <+>: mov DWORD PTR [esp+0x24],eax foo_t *p = container_of(p3, foo_t, m_char);
0x08048455 <+>: mov eax,DWORD PTR [esp+0x24]
0x08048459 <+>: mov DWORD PTR [esp+0x28],eax
0x0804845d <+>: mov eax,DWORD PTR [esp+0x28]
0x08048461 <+>: sub eax,0x6
0x08048464 <+>: mov DWORD PTR [esp+0x2c],eax
#
# --- L21's output from "gcc -E foo.c" ---
# 001 foo_t *p = ({
# 002 const typeof( ((foo_t *))->m_char ) *__mptr = (p3);
# 003 (foo_t *)( (char *)__mptr - ((size_t)&((foo_t *))->m_char) );
# 004 });
#
结合汇编代码阅读下面两行,会更好懂:-)
const typeof( ((foo_t *))->m_char ) *__mptr = (p3);
(foo_t *)( (char *)__mptr - ((size_t)&((foo_t *))->m_char) );
小结
- typeof (VAR): 获取变量VAR的数据类型, (注意typeof是gcc支持的扩展关键字)
- offsetof(TYPE, MEMBER): 获取成员变量MEMBER在其所在的结构体(类型为TYPE)里的偏移
- container_of(PTR, TYPE, MEMBER): 根据成员变量MEMBER的内存首地址PTR, 反推出其所在的结构体(类型为TYPE)变量的内存首地址
参考资料
1. $ man -s3 offsetof

2. C Operators (which are copied from book C Programming: A Modern Approach, Second Edition) (快速查询C操作符的脚本戳这里)
APPENDIX A
C Operators --------------------------------------------------------------------------------
Precedence Name Symbol(s) Associativity
--------------------------------------------------------------------------------
1 Array subscripting [] Left
1 Function call () Left
1 Structure and union member . -> Left
1 Increment (postfix) ++ Left
1 Decrement (postfix) -- Left
--------------------------------------------------------------------------------
2 Increment (prefix) ++ Right
2 Decrement (prefix) -- Right
2 Address & Right
2 Indirection * Right
2 Unary plus + Right
2 Unary minus - Right
2 Bitwise complement ~ Right
2 Logical negation ! Right
2 Size sizeof Right
--------------------------------------------------------------------------------
3 Cast () Right
--------------------------------------------------------------------------------
4 Multiplicative * / % Left
--------------------------------------------------------------------------------
5 Additive + - Left
--------------------------------------------------------------------------------
6 Bitwise shift << >> Left
--------------------------------------------------------------------------------
7 Relational < > <= >= Left
--------------------------------------------------------------------------------
8 Equality == != Left
--------------------------------------------------------------------------------
9 Bitwise and & Left
--------------------------------------------------------------------------------
10 Bitwise exclusive or ^ Left
--------------------------------------------------------------------------------
11 Bitwise inclusive or | Left
--------------------------------------------------------------------------------
12 Logical and && Left
--------------------------------------------------------------------------------
13 Logical or || Left
--------------------------------------------------------------------------------
14 Conditional ?: Right
--------------------------------------------------------------------------------
15 Assignment = *= /= %= Right
+= -= <<= >>=
&= ^= |=
--------------------------------------------------------------------------------
16 Comma , Left
-------------------------------------------------------------------------------- 735
typeof, offsetof 和container_of的更多相关文章
- [dev]typeof, offsetof 和container_of
转一篇文章.写的比较好,浅显易懂,还画了图. https://www.cnblogs.com/idorax/p/6796897.html 概况一下: container_of用到了typeof和off ...
- linux中offsetof与container_of宏定义
linux内核中offsetof与container_of的宏定义 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->M ...
- (转)offsetof与container_of宏[总结]
1.前言 今天在看代码时,遇到offsetof和container_of两个宏,觉得很有意思,功能很强大.offsetof是用来判断结构体中成员的偏移位置,container_of宏用来根据成员的地址 ...
- offsetof与container_of宏[总结]
1.前言 今天在看代码时,遇到offsetof和container_of两个宏,觉得很有意思,功能很强大.offsetof是用来判断结构体中成员的偏移位置,container_of宏用来根据成员的地址 ...
- typeof、offsetof、container_of的解释
链表是内核最经典的数据结构之一,说到链表就不得不提及内核最经典(没有之一)的宏container_of. container_of似乎就是为链表而生的,它的主要作用是根据一个结构体变量中的一个域成员变 ...
- typeof, offsetof, container_of宏
container_of宏实现如下: #define container_of(ptr, type, member) ({ \ )->member ) *__mptr = (ptr); \ (t ...
- C语言笔记(结构体与offsetof、container_of之前的关系)
关于结构体学习,需要了解:结构体的定义和使用.内存对齐.结构体指针.得到结构体元素的偏移量(offsetof宏实现) 一.复习结构体的基本定义和使用 typedef struct mystruct { ...
- offsetof与container_of宏分析
offsetof宏:结构体成员相对结构体的偏移位置 container_of:根据结构体成员的地址来获取结构体的地址 offsetof 宏 原型: #define offsetof(TYPE, MEM ...
- 对offsetof、 container_of宏和结构体的理解
offsetof 宏 #include<stdio.h> #define offsetoff(type, member) ((int)&((type*)0)->me ...
随机推荐
- 在linux中使用包管理器安装node.js
网上文章中,在linux下安装node.js都是使用源码编译,其实node的github上已经提供了各个系统下使用各自的包管理器(package manager)安装node.js的方法. 1. 在U ...
- django model中的save()方法
Model.save(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None) id和pk ...
- AutoMapper在C#中的有趣应用
最近发现了一个比较有趣的东西 AutoMapper,主要将Model转换为DTO,DTO更注重数据,对领域对象进行合理封装,从而不会将领域对象的行为过分暴露给表现层. 先来看一点实例,两个类之间的映射 ...
- IIS隐藏网站
IIS隐藏网站 1.站点建立一个文件夹:Test 2.在F盘新建Web文件夹(放要隐藏的网站) 3.右键Test文件夹-新建虚拟目录,虚拟目录指向步骤2 4.删除Test文件夹即可
- iOS Keychain 跨应用
Keychain 可以用来持久保存一些信息.通常每个应用都有自己的 Keychain Access.但有时你会需要多个应用共用一些信息.这时需要创建 Keychain Access Group. Ke ...
- Python----初次见面,请多关照!
1.计算机的最基本认识 CPU(大脑) 3GHZ + 内存(DDR4) + 主板 + 电源(心脏)+ 显示器 + 键盘 +鼠标+ 显卡 + 硬盘 80MB/s 操作系统分为: windows 家用 l ...
- GO学习笔记 - 没有条件的 switch 同 switch true 一样。
官方教程:https://tour.go-zh.org/flowcontrol/11 没有条件的 switch 同 switch true 一样. 这一构造使得可以用更清晰的形式来编写长的 if-th ...
- Java多线程(汇聚页)
Java多线程(汇聚页) Java多线程总结
- CF1109DSasha and Interesting Fact from Graph Theory(数数)
题面 传送门 前置芝士 Prufer codes与Generalized Cayley's Formula 题解 不行了脑子已经咕咕了连这么简单的数数题都不会了-- 首先这两个特殊点到底是啥并没有影响 ...
- 记一次优化ansible inventory的小例子
起因: 阿里云新扩容一批机器,要对上面的flume配置做修改 之前的inventory是这样子的 [user@vip10-ali-tj-console host_vars]$ sdiff vip10- ...