要理解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. 简单的ListView中item图片异步加载

    前言:     在android开发当中,从目标地址获取图片往往都是采用异步加载的方法.当完全加载完图片后在进行显示,也有些是直接将加载的图片一点一点的显示出来. 这两个区别只是对流的处理不同而已.现 ...

  2. maven多模块启动required a bean of type com.xxx.xxx.service that could not be found.

    Description: Field testService in com.xxx.xxx.api.controller.TestController required a bean of type ...

  3. window系统JAVA开发环境的搭建

    1.java JSK工具包安装教程http://www.runoob.com/java/java-environment-setup.html 2.Eclipase编辑器安装包教程 http://ww ...

  4. DataGridViewComboBoxColumn值无效解决方法

    值无效,可能是你下拉框选项,没有这样的值,而你却设置这个值. dataGridView1.Rows[i].Cells[1].Value = "选项一"; 解决方法就是在窗体的构造函 ...

  5. insert into 的另一种添加插入新行方式

    语法 1 插入一行 insert into table (field1,field2.....) select value1,value2........; 2 插入多行 insert into ta ...

  6. JSON is undefined. Infopath Form People Picker in SharePoint 2013

    After some analysis, we found that, this is a known defect with the Microsoft and it is being fixed ...

  7. win32拖拽编程

    本文由作者邹启文授权网易云社区发布. 在邮箱大师PC版中,我们需要实现一个功能:账号和邮件夹拖拽排序. 准备 封装win32 API.我们使用到的API有, ImageList_Create.Imag ...

  8. vuejs 添加事件时出现TypeError: n.apply is not a function

    vuejs项目中给表单元素添加事件时出现了TypeError: n.apply is not a function的错误,后来发现错误原因时处理事件的函数名和data中定义的变量名相同 当给事件添加处 ...

  9. 深入了解java虚拟机(JVM) 第八章 常见的jvm调优策略

    一般来说,jvm的调优策略是没有一种固定的方法,只有依靠我们的知识和经验来对项目中出现的问题进行分析,正如吉德林法则那样当你已经把问题清楚写出来,就已经解决了一半.虽然JVM调优中没有固定的策略,但是 ...

  10. springboot 使用itextpdf 框架实现多个图片合成一个pdf文件

    以下两个方法引入头 import com.lowagie.text.*; import com.lowagie.text.pdf.PdfWriter; import org.apache.pdfbox ...