在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. Hadoop 3.1.1 - Yarn - 使用 FPGA

    在 Yarn 上使用 FPGA 前提 YARN 目前只支持通过 IntelFpgaOpenclPlugin 发布的 FPGA 资源 YARN NodeManager 所在的机器上必须预先安装供应商的驱 ...

  2. 百度地图API基本使用(一)

    本文系作者 chaoCode原创,转载请私信并在文章开头附带作者和原文地址链接. 违者,作者保留追究权利. 前言 由于最近项目有需要,所以最近开始研究百度地图API的使用,简单的介绍一下百度地图Jav ...

  3. Mybatis学习笔记-动态SQL

    概念 根据不同环境生成不同SQL语句,摆脱SQL语句拼接的烦恼[doge] 本质:SQL语句的拼接 环境搭建 搭建数据库 CREATE TABLE `blog`( `id` VARCHAR(50) N ...

  4. AlarmManager定时提醒的那些坑

    https://blog.csdn.net/zackratos/article/details/53243595 https://blog.csdn.net/bingshushu/article/de ...

  5. CF427B

    没人用ST表么?他比线段树快. 考虑先把ST表跑下来,然后循环一遍区间的起点,看一下这个区间的最大值,和 \(t\) 比较一下即可. 然后这题就做完了.ST表裸题. int f[2000010][21 ...

  6. 我是如何在一晚上拿到阿里巴巴Android研发offer的?

    图文无关 开篇 我找工作时是2018年. 那一年,BAT大量缩招,就业形势严峻,互联网寒冬消息蔓延. 最终我经过激烈角逐拼下了几个大厂offer,回顾往事,觉得分享出来,也许对你能有所借鉴. 简历 这 ...

  7. 一、MinIO的基本概念

    MinIO的官方网站非常详细,以下只是本人学习过程的整理 一.MinIO的基本概念 二.Windows安装与简单使用MinIO 三.Linux部署MinIO分布式集群 四.C#简单操作MinIO 一. ...

  8. STP相关概念

    1)桥ID(Bridge ID)=Bridge Priority+MAC 2)  端口ID(Port ID)=Port Priority+Port No 3)桥根 4)非桥根 5)根端口 6)指定端口 ...

  9. 【工作篇】再次熟悉 SpringMVC 参数绑定

    前言 主要现在项目中使用的参数绑定五花八门的,搞得很头大,例如有些用字符串接收日期,用字符串接受数组等等,完全没有利用好 SpringMVC 的优势,这里自己也总结一下,免得到时又要百度谷歌查找. 以 ...

  10. NLP与深度学习(一)NLP任务流程

    1. 自然语言处理简介 根据工业界的估计,仅有21% 的数据是以结构化的形式展现的[1].在日常生活中,大量的数据是以文本.语音的方式产生(例如短信.微博.录音.聊天记录等等),这种方式是高度无结构化 ...