在linux 内核编程中,会经常见到一个宏函数container_of(ptr,type,member), 但是当你通过追踪源码时,像我们这样的一般人就会绝望了(这一堆都是什么呀? 函数还可以这样定义??? 怎么还有0呢???  哎,算了,还是放弃吧。。。)。 这就是内核大佬们厉害的地方,随便两行代码就让我们怀疑人生,凡是都需要一个过程,慢慢来吧。

其实,原理很简单:  已知结构体type的成员member的地址ptr,求解结构体type的起始地址。

type的起始地址 = ptr - size      (这里需要都转换为char *,因为它为单位字节)。

到此,该函数已经讲完,是不是很简单??? 其实也不是,这里并没有提到size如何计算,而令我们头晕的正是这里。

好吧,先上container of函数原型:

<span style="color:#330099"><strong>#define container_of(ptr, type, member) ({              \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})</strong></span>

其次为 offserof 函数原型:

<strong><span style="color:#330099">#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)</span></strong>

怎么样,是不是很炫?  好吧,下面开始揭开面纱:

  (一)0 指针的使用    (自己给的名字,不知有木问题)

让事实说话:

<strong>#include<stdio.h>

struct test
{
char i ;
int j;
char k;
}; int main()
{
struct test temp;
printf("&temp = %p\n",&temp);
printf("&temp.k = %p\n",&temp.k);
printf("&((struct test *)0)->k = %d\n",((int)&((struct test *)0)->k)); }</strong>

编译运行,可以得到如下结果:

<strong><span style="color:#6600cc">&temp = 0xbf9815b4
&temp.k = 0xbf9815bc
&((struct test *)0)->k = 8</span></strong>

什么意思看到了吧,自定义的结构体有三个变量:i,j,k。 因为有字节对齐要求,所以该结构体大小为4bytes * 3 =12 bytes.   而&((struct test *)0)->k 的作用就是求 k到结构体temp起始地址的字节数大小(就是我们的size)。在这里0被强制转化为struct test *型, 它的作用就是作为指向该结构体起始地址的指针就是作为指向该结构体起始地址的指针就是作为指向该结构体起始地址的指针, 而&((struct test *)0)->k  的作用便是求k到该起始指针的字节数。。。其实是求相对地址,起始地址为0,则&k的值便是size大小(注:打印时因为需要整型,所以有个int强转)所以我们便可以求我们需要的 size 了  。 好吧,一不小心把 offsetof() 函数的功能给讲完了:::

<strong><span style="color:#330099">#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)</span></strong>

这次再看就顺眼了吧(底层为什么是这样我还是不懂。。。只知道这样确实可以) ,  所以offsetof()的作用就是求我们梦寐以求的size, 并以size_t形式返回(size_t: 无符号整型)。

(二) 内核编程的严谨性  

<span style="color:#330099"><strong>#define container_of(ptr, type, member) ({              \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})</strong></span>

这里我们只看第二行:

<span style="color:#330099"><strong>const typeof( ((type *)0)->member ) *__mptr = (ptr);  </strong></span>

它的作用是什么呢? 其实没什么作用(勿喷勿喷,让我把话说完),但就形式而言 _mptr = ptr,  那为什么要要定义一个一样的变量呢??? 其实这正是内核人员的牛逼之处:如果开发者使用时输入的参数有问题:ptr与member类型不匹配,编译时便会有warnning, 但是如果去掉改行,那个就没有了,而这个警告恰恰是必须的(防止出错有不知道错误在哪里)。。。这严谨性可以吧

<span style="color:#330099"><strong>typeof( ((type *)0)->member )</strong></span>

它的作用是获取member的类型仅此而已。至此基本结束

(三) 总结

container_of(ptr, type,member)函数的实现包括两部分:

1.  判断ptr 与 member 是否为同意类型

2.  计算size大小,结构体的起始地址 = (type *)((char *)ptr - size)   (注:强转为该结构体指针)

现在我们知道container_of()的作用就是通过一个结构变量中一个成员的地址找到这个结构体变量的首地址。

    container_of(ptr,type,member),这里面有ptr,type,member分别代表指针、类型、成员。

如果还是看不懂container_of()函数,那算我输的更多相关文章

  1. 还不懂mysql的undo log和mvcc?算我输!

    最近一直没啥时间写点东西,坚持分享真的好难,也不知道该分享点啥,正好有人要问我这些东西,所以腾出点时间,写一下这个主题.同样本篇可以给读者承诺,听不懂或者没收获算我输,哈哈! 众所周知,mysql中读 ...

  2. 还看不懂同事的代码?Lambda 表达式、函数接口了解一下

    当前时间:2019年 11月 11日,距离 JDK 14 发布时间(2020年3月17日)还有多少天? // 距离JDK 14 发布还有多少天? LocalDate jdk14 = LocalDate ...

  3. thinkphp学习笔记10—看不懂的路由规则

    原文:thinkphp学习笔记10-看不懂的路由规则 路由这部分貌似在实际工作中没有怎么设计过,只是在用默认的设置,在手册里面看到部分,艰涩难懂. 1.路由定义 要使用路由功能需要支持PATH_INF ...

  4. QQ地图api里的 地址解析函数 看不懂 javascript_百度知道

    QQ地图api里的 地址解析函数 看不懂 javascript_百度知道     QQ地图api里的 地址解析函数 看不懂 javascript    2011-09-18 12:18     匿名 ...

  5. 还看不懂同事的代码?超强的 Stream 流操作姿势还不学习一下

    Java 8 新特性系列文章索引. Jdk14都要出了,还不能使用 Optional优雅的处理空指针? Jdk14 都要出了,Jdk8 的时间处理姿势还不了解一下? 还看不懂同事的代码?Lambda ...

  6. 小白学习React官方文档看不懂怎么办?3.元素渲染

    直接上代码 const element = <h1>Hello, world</h1>; ReactDOM.render(     element,      document ...

  7. 递归实现深拷贝( 只要学过js递归,看不懂找我包会 )

    要用递归实现深拷贝,首先说说什么是深拷贝和浅拷贝 浅拷贝:一个值赋给另一个值,当原先的值不改变地址的情况下改变数据,另一个值跟着变 深拷贝:一个值赋给另一个值,当原先的值不改变地址的情况下改变数据,另 ...

  8. (六)羽夏看C语言——函数

    写在前面   由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...

  9. 对于挑战书上的很久之前都看不懂的DP看懂的突破

    突破一..牢记问题概念 并且牢记dp状态方程 突破二..一直有一个求和dp转化成O1dp递推的式子看不懂.. 看不懂的原因是..没有分清求和符号作用的范围 提醒:以后遇到求和符号一定明确其求和的式子的 ...

随机推荐

  1. IOS自动化测试环境搭建(Python & Java)

         一.前言 IOS的App自动化测试与Android的一样,也可以用appium来进行.但是IOS自动化依赖苹果的osx系统.Xcode构建等,且封闭的系统需要苹果开发者账号才可以驱动真机.A ...

  2. 浏览器中hook对象属性

    先获取window对象属性 来源: 夜幕爬虫安全论坛 原文链接: http://bbs.nightteam.cn/thread-485.htm?orderby=desc&user=7

  3. Node.js躬行记(8)——通用接口

    一.GraphQL 最近服务端的同事分享了GraphQL,他分享的目的就是要把我们与他们的数据库隔离,这么做我们也求之不得. 我们组目前维护着一个后台管理系统,会直接读取数据库中的表,如果能隔离的话, ...

  4. for循环中++i和i++的区别

    语法 for (语句1; 语句2; 语句3) { 被执行的代码块 } 语句 1 在循环(代码块)开始前执行 语句 2 定义运行循环(代码块)的条件 语句 3 在循环(代码块)已被执行之后执行(这就是循 ...

  5. Java 执行控制流程

    1.带标签的break会中断并跳出标签所指的循环: 2.带标签的continue会中断本次循环,并开始标签所指处循环的下一轮循环.

  6. 在Linux系统上查找文件

    Find命令 格式:find <指定搜索范围> <指定条件> <指定动作> 其中搜索范围是一个目录名,指定条件包括文件名.文件属性(修改时间所属用户等).所在位置特 ...

  7. CodeReview of JDK Source Code

    jdk1.6.0_35/src.zip, only java package is reviewd(full package review caused a OutofMemory on java h ...

  8. Python--数据存储

    新建测试文档测试.txt内容如下: 张三:今天天气真好. 李四:是的,真的太好了. 张三:阳光明媚. 李四:鸟语花香. 王五:装逼现场:请带好安全帽 张三:难得好天气,今天就不偷懒了. 李四:能把乞讨 ...

  9. ACM学习笔记:可持久化线段树

    title : 可持久化线段树 date : 2021-8-18 tags : 数据结构,ACM 可持久化线段树 可以用来解决线段树存储历史状态的问题. 我们在进行单点修改后,线段树只有logn个(一 ...

  10. cmseasy&内网渗透 Writeup

    某CTF内网渗透 题目:www.whalwl.site:8021 目录 cmseasy 内网横向渗透 cmseasy 简单看一下网站架构 Apache/2.4.7 (Ubuntu) PHP/5.5.9 ...