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 ...
随机推荐
- 开源WebGIS实施方案(三):Shapefile数据导入到PostGIS
PostGIS新版中提供了一个可视化的工具,用于Shapefile数据的导入和导出,极大的方便了使用者的操作. 创建空间数据库 以具有创建用户权限的账号登录pgAdminIII,连接到数据库 创建一个 ...
- ubuntu 安装 hubicfuse
如果你没有gcc,请先安装gcc: 1: apt-get install build-essential 1. 从github上clone源码: https://github.com/TurboGit ...
- cron和crontab
crontab -l 列出目前的计划任务(时程表) crontab -e 编辑计划任务 计划任务的格式如下: f1 f2 f3 f4 f5 program 其中 f1 是表示分钟,f2 表示小时, ...
- PostSharp 结合 log4net 自动记录日志
环境: VS 2012 PostSharp-4.1.28 (下载地址)https://visualstudiogallery.msdn.microsoft.com/a058d5d3-e654-43f ...
- mysql--对行(表中数据)的增删改查
一.插入数据(增加)insert 1.插入数据(顺序插入) 语法一: INSERT INTO 表名(字段1,字段2,字段3…字段n) VALUES(值1,值2,值3…值n); #指定字段来插入数据,插 ...
- Web Api 内部数据思考 和 利用http缓存优化 Api
在上篇<Web Api 端点设计 与 Oauth>后,接着我们思考Web Api 的内部数据: 其他文章:<API接口安全加强设计方法> 第一 实际使用应该返回怎样的数据 ? ...
- 百度地图API鼠标获取坐标
var map = new BMap.Map('map'); var poi = new BMap.Point(112.53, 37.87); map.enableScrollWheelZoom(); ...
- PHP 函数功能参考
basename() 返回路径中的文件名部分 chgrp() 改变文件组 chmod() 改变文件模式 chown() 改变文件所有者 clearstatcache() 清除文件状态缓存 copy() ...
- Generating an arbitrary digit password dictionary
原理说明:以增量方式从开始到结束! 实现方法:CMD命令 特点:纯数字 语法: FOR /L %variable IN (start,step,end) DO command [command-par ...
- 【微信小程序】——wxss引用外部CSS文件及iconfont
小程序引入外部文件的方式是:@import "*/*.wxss"; 因为业务需要,正在开发的小程序中需要使用iconfont,很容易想到了H5的引入方式: ```` @font-f ...