链表是内核最经典的数据结构之一,说到链表就不得不提及内核最经典(没有之一)的宏container_of。

container_of似乎就是为链表而生的,它的主要作用是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针,最典型的应用就是根据链表节点获取链表上的元素对象。

container_of的宏定义如下:
  1. #define container_of(ptr, type, member) ({            \
  2. const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
  3. (type *)( (char *)__mptr - offsetof(type,member) );})

这个宏乍一看有点瘆人,宏里面包含了两个关键字:typeof和offsetof:

typeof是GNU对C新增的一个扩展关键字,用于获取一个对象的类型,在很多时候我们处理的对象通常是一个指针,而此时如果想知道指针所指向的对象的类型,typeof就派上用场了,详见GNU的官方文档:http://gcc.gnu.org/onlinedocs/gcc/Typeof.html

 
现在看container_of宏的第一条语句:
  1. const typeof( ((type *)0)->member ) *__mptr = (ptr); \

创建一个类型为const typeof( ((type *)0)->member ) *,即
类型为type结构的member域所对应的对象类型的常指针__mptr,并用ptr初始化之,这样一来,__mptr就指向了某一个type的
member域。因为数据结构是顺序存储的,此时如果知道member在type结构中的相对偏移,那么用__mptr减去此偏移便是ptr所属的
type的地址,因此宏的第二条语句应运而生:

  1. (type *)( (char *)__mptr - offsetof(type,member) );})

另一个主角出场了---offsetof,返回一个数据域在它所属的数据结构中的相对偏移,单位是size_t,宏定义如下:

  1. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

将地址0强制转换为type *,那么0指向某一个type类型的对象,也就是此type对象的地址,那么(TYPE *)0)->MEMBER就是type的member域,现在取址后&((TYPE *)0)->MEMBER 就
是member域的地址,因为type的地址为0,则member的地址实质上就是它相对于type地址的偏移。这里为什么可以这样实现而不会出错呢?有
两点原因,其一,地址0是在编译器编译时已经指定好了的,其二,这里全部都是取址操作,并没内存数据访问,因此不会存在非法访问内存的问题。

这样贯穿起来看,其实container_of的实现其实也是蛮清晰的,记得某位兄台说过一句话:一切事物在你看清楚它的本质之前,对你而言总是朦胧的。
http://blog.chinaunix.net/uid-27714502-id-3335236.html

typeof、offsetof、container_of的解释的更多相关文章

  1. typeof, offsetof, container_of宏

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

  2. [dev]typeof, offsetof 和container_of

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

  3. typeof, offsetof 和container_of

    要理解Linux中实现的双向循环链表("侵入式"链表),首先得弄明白宏container_of. 本文尝试从gcc的关键字typeof和宏offsetof入手,循序渐进地剖析宏co ...

  4. 各大互联网公司前端面试题(js)

    对于巩固复习js更是大有裨益.    初级Javascript: 1.JavaScript是一门什么样的语言,它有哪些特点? 没有标准答案. 2.JavaScript的数据类型都有什么? 基本数据类型 ...

  5. 深入理解es6-Promise对象

    前言     在之前翻博客时,看到promise,又重读了一边,突然发现理解很浅,记的笔记也不是很好理解,又重新学习promise,加深理解,学以致用     在promise出来之前,js常用解决异 ...

  6. c&c++中的宏

    1 c&c++中的宏 do {...} while (0); offsetof & container_of 2 引用 [1] do {...} while (0) 在宏定义中的作用 ...

  7. what's the help of "unnecessary" pointer comparison

    引述自http://c-programming.itags.org/q_c-programming-language_191518.html 源代码中的宏min中使用了 (void) (&_x ...

  8. 我的JavaScript笔记--数据类型,预编译,闭包

     在我们js中存储数据的空间可以分为两种,堆内存和栈内存 堆内存:我们定义的那些引用数据类型的数据都会在堆内存中开辟空间. 栈内存:我们运行的js代码还有我们定义的基本数据类型,都直接在栈内存中存储 ...

  9. 如何区分null和undefined

    null和undefined是两种数据类型, 如果硬要区分的话. null是一种类型, 赋值变量为null型. 未定义的变量, 即为undefined. var a = null a // null ...

随机推荐

  1. qml ios长按晃动

    WidgetModel.qml import QtQuick 1.0 ListModel { ListElement { icon: "Images/widget1.png"; g ...

  2. node.js 对接公众平台

    http://www.tfan.org/wp-content/uploads/使用-Nodejs-和-MongoDB-开发高性能微信公众平台应用.pdf

  3. The Python web services developer: XML-RPC for Python

    原文地址:http://www.ibm.com/developerworks/webservices/library/ws-pyth10/index.html 摘要:概括地说,您可以将 XML-RPC ...

  4. android应用程序如何调用支付宝接口(转)

    最近在做一个关于购物商城的项目,项目里面付款这块我选的是调用支付宝的接口,因为用的人比较多. 在网上搜索了以下,有很多这方面的教程,但大部分教程过于陈旧,而且描述的过于简单.而且支付宝提供的接口一直在 ...

  5. jQuery中的quickExpr

    jQuery 源码中的 quickExpr 是用来检测参数 selector 是否是复杂的 HTML 代码(如“abc<div>”)或 #id,匹配结果存放在数组 match 中 // A ...

  6. UVa 10870 - Recurrences

    http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&p ...

  7. 二模 (3) day2

    第一题: 题目大意:(难以概括,就不贴了把.) 解题过程: 1.担心被精度问题恶心,就把平均数的地方乘了N,这样只有最后计算的时候才会是小数.. 2.数组保存的时候蛋疼的 没改成double.结果全部 ...

  8. ECMAScript 6新特性(1)数组篇

    数组现有的方法: .concat():连接两个或更多的数组,并返回结果. .join():把数组的所有元素放入一个字符串.元素通过指定的分隔符进行分隔. .pop():删除并返回数组的最后一个元素 . ...

  9. LICEcap

    LICEcap是一款简洁易用的动画屏幕录制软件,它可将屏幕录像的内容直接保存为高质量(每帧颜色数量可超过256)GIF动态图片格式.并且支持特别标记鼠标操作动态效果.

  10. C语言实例代码

    绘制余弦曲线和直线 #include #include int main() { double y; int x,m,n,yy; for(yy=0;yy<=20;yy++) {y=0.1*yy; ...