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 ...
随机推荐
- Android-bindService本地服务-音乐播放-上
播放音乐的行为写在服务里,Activity去调用Service里面到方法,进行音乐播放,当Activity结束后,音乐播放器停止播放 界面: MainActivity: package liudeli ...
- 利用SQL表生成按日期序列的唯一ID
1. 创建一个表,用于存现在最大的ID SELECT [ID],[PreFix],[Code] FROM [DocumentNO] 2. 增加SP,利用锁表,生成相应的ID Create PROCED ...
- 通过一个例子感受C# 6.0新特性
微软在Visual Studio 2015中更新C#语言到6.0,添加了很多很好的特性,以使C#语言继续跻身于最优秀语言之行列.下面通过一个例子快速感受一下C# 6.0的新特性,以下程序在VS2015 ...
- http协议与https协议的区别
1.前言 超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可 ...
- vuejs 添加事件时出现TypeError: n.apply is not a function
vuejs项目中给表单元素添加事件时出现了TypeError: n.apply is not a function的错误,后来发现错误原因时处理事件的函数名和data中定义的变量名相同 当给事件添加处 ...
- CentOS7.x下安装VNC
1.检查是否安装VNC rpm -q tigervnc tigervnc-server 2.安装X-Window yum check-update yum groupinstall "X W ...
- video视频内容填充整个播放空间方法
关于video视频内容填充整个播放空间方法一般上传的视频都没法占满video,看起来很不美观,解决办法很简单video{ object-fit:fill;}
- php对ip地址的处理
public function actions() { $url = "http://ip.taobao.com/service/getIpInfo.php?ip=".self:: ...
- Lucene.Net+盘古分词器(详细介绍)
本章阅读概要1.Lucenne.Net简介2.介绍盘古分词器3.Lucene.Net实例分析4.结束语(Demo下载)Lucene.Net简介 Lucene.net是Lucene的.net移植版本,是 ...
- jmeter ——JDBC Request中从数据库中读两个字段给接口取值
前置条件数据库: 给接口传:tid和shopid这俩字段 直接从JDBC Request开始: Variable name:这里写入数据库连接池的名字(和JDBC Connection Configu ...