要理解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的更多相关文章

  1. [dev]typeof, offsetof 和container_of

    转一篇文章.写的比较好,浅显易懂,还画了图. https://www.cnblogs.com/idorax/p/6796897.html 概况一下: container_of用到了typeof和off ...

  2. linux中offsetof与container_of宏定义

    linux内核中offsetof与container_of的宏定义 #define offsetof(TYPE, MEMBER)    ((size_t) &((TYPE *)0)->M ...

  3. (转)offsetof与container_of宏[总结]

    1.前言 今天在看代码时,遇到offsetof和container_of两个宏,觉得很有意思,功能很强大.offsetof是用来判断结构体中成员的偏移位置,container_of宏用来根据成员的地址 ...

  4. offsetof与container_of宏[总结]

    1.前言 今天在看代码时,遇到offsetof和container_of两个宏,觉得很有意思,功能很强大.offsetof是用来判断结构体中成员的偏移位置,container_of宏用来根据成员的地址 ...

  5. typeof、offsetof、container_of的解释

    链表是内核最经典的数据结构之一,说到链表就不得不提及内核最经典(没有之一)的宏container_of. container_of似乎就是为链表而生的,它的主要作用是根据一个结构体变量中的一个域成员变 ...

  6. typeof, offsetof, container_of宏

    container_of宏实现如下: #define container_of(ptr, type, member) ({ \ )->member ) *__mptr = (ptr); \ (t ...

  7. C语言笔记(结构体与offsetof、container_of之前的关系)

    关于结构体学习,需要了解:结构体的定义和使用.内存对齐.结构体指针.得到结构体元素的偏移量(offsetof宏实现) 一.复习结构体的基本定义和使用 typedef struct mystruct { ...

  8. offsetof与container_of宏分析

    offsetof宏:结构体成员相对结构体的偏移位置 container_of:根据结构体成员的地址来获取结构体的地址 offsetof 宏 原型: #define offsetof(TYPE, MEM ...

  9. 对offsetof、 container_of宏和结构体的理解

    offsetof 宏 #include<stdio.h> #define offsetoff(type, member)      ((int)&((type*)0)->me ...

随机推荐

  1. FIREDAC(DELPHI10 or 10.1)提交数据给ORACLE数据库的一个不是BUG的BUG

    发现FIREDAC(DELPHI10 or 10.1)提交数据给ORACLE数据库的一个不是BUG的BUG,提交的表名大小写是敏感的. 只要有一个表名字母的大小写不匹配,ORACLE就会认为是一个不认 ...

  2. TSQL--NULL值和三值逻辑

    在SQL SERVER 中逻辑表达式存在三种值:TRUE+FALSE+UNKNOWN.UNKNOW可以理解为不确定,既不是TRUE又不是FALSE的表达式,主要由与NULL相关的逻辑判断引起,值为NU ...

  3. 多次grep 没有看到输出

    tail -f xxx.log | grep aaaa | grep bbbb 发现没有日志输出 但log中的那条记录包含aaaa 和 bbbb,就是说tail  -f xxx.log | grep ...

  4. docker查看挂载目录命令

    docker inspect -f "{{.Mounts}}"  692691b7416 692691b7416为containerId

  5. 在相应目录下新建或读取xml文件

    string path = AppDomain.CurrentDomain.BaseDirectory+"UserContent1.xml"; //判断相应路径下文件是否存在 不存 ...

  6. Mustache 使用说明

    Mustache 使用说明 最近在升级SinGooCMS到MVC架构.管理前端使用了Mustache模板,把使用心得记录一下! 一.官网http://mustache.github.io/https: ...

  7. WebService-php- 1(16)

    最近看了挺多关于php中webservice的资料,感谢燕十八的分享,帮助了我构建服务端的过程.将学习笔记记录如下,其中包含燕十八的笔记. WebService 1 快速了解WebService 通俗 ...

  8. Maven 项目中使用mybatis-generator生成代码

    在使用Maven构建SSM项目时,使用mybatis-generator插件自动生成代码 一.目录结构 bean:用来存放生成的实体类 dao:用来存放生成的 *mapper.java文件 mappe ...

  9. sql server中的 trimtrailingblanks

    使用sp_help 查出 发现有个这个属性, 如何修改呢? SET ANSI_PADDING ONAlter Table Sys_users_History Alter   column PveSit ...

  10. 【Oracle 12c】CUUG OCP认证071考试原题解析(35)

    35.choose the best answer View the Exhibit and examine the description of the EMPLOYEES table. Evalu ...