在linux内核链表中,会遇到两个宏。

在include/linux/stddef.h中,有这样的定义

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

这里的TYPE表示某个结构体类型,MEMBER表示结构体中的一个成员,这个宏求出了成员在结构体中的位置偏移(以字节为单位)

如果你还不理解,我们举个例子吧。

struct student
{char name[20];unsigned char age;
};int main(void)
{int off = offsetof(struct student, age);printf("off = %d\n",off);
}

运行结果是off = 20

其实宏里面的0只是一种特殊情况而已。对这个宏的解释是:假设结构体处于0x1234这个地址,

((TYPE*)0x1234 )-> MEMBER
->比类型转换的优先级高,所以要加括号。上面就得到了成员,再取地址,得到成员的地址

&((TYPE*)0x1234 )-> MEMBER

不用加括号,因为&的优先级比较低

再来一个类型转换

(size_t)&((TYPE*)0x1234 )-> MEMBER

终于得到成员的起始地址了,还没有完,既然算偏移,就要减去结构体的起始地址

(size_t)&((TYPE*)0x1234 )-> MEMBER  - 0x1234

不难看出,这里的0x1234换成什么数字都可以,因为偏移是和起始地址无关的。

不信的话可以把这个宏定义改一改,再测试一下

#define offsetof(TYPE, MEMBER) (  (size_t) &((TYPE *)0x2222)->MEMBER  -   0x2222  )struct student
{char name[20];unsigned char age;
};int main(void)
{int off = offsetof(struct student, age);printf("off = %d\n",off);
}

改成0x2222后,结果还是20.

既然什么数字都可以,那就改成0吧,于是后面的-0就可以省略了。于是就得到了开头的那个宏。

下面我们说另外一个宏。

/**

827 * container_of - cast a member of a structure out to the containing structure

828 * @ptr:    the pointer to the member.

829 * @type:   the type of the container struct this is embedded in.

830 * @member: the name of the member within the struct.

831  *

832  */

833 #define container_of(ptr, type, member) ({          \

834     const typeof( ((type *)0)->member ) *__mptr = (ptr);    \

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

这个宏就是从一个成员的地址得到这个结构体的地址,俗称小指针转大指针。

继续举例子。

struct student
{char name[20];unsigned char age;
};int main(void)
{struct student stu = {"wangdong",22};printf("&stu = %p\n",&stu);printf("&stu.age = %p\n",&stu.age);struct student *p = container_of(&stu.age, struct student, age);printf("p= %p\n",p);
}

运行结果为

&stu = 0x7fff53df9c40

&stu.age = 0x7fff53df9c54

p= 0x7fff53df9c40

第一行和第三行的值是一致的。
如果你不理解这个宏的定义,我们先简化一下它,这样写
#define container_of(ptr, type, member)             (  (type *)( (char *)ptr - offsetof(type,member) )  )
(char *)ptr 成员的地址
offsetof(type,member)  成员的偏移
二者相减,就是结构体的起始地址,最后再加个强制类型转换。

可是为什么不是这样写的呢,而是要多出来一行

const typeof( ((type *)0)->member ) *__mptr = (ptr);

没有这行到底行不行,其实也行,用上面的例子,去掉这行,也可以得到一样的结果。

先看看这行什么意思吧, ((type *)0)->member 这是成员,typeof( ((type *)0)->member ) 得到了成员的类型,假设就是unsigned char类型,

const typeof( ((type *)0)->member ) *__mptr 定义了一个这个类型的指针

const unsigned char * __mptr = (ptr); 赋值给定义的这个指针。

我苦思冥想,又结合网上的资料,认为这样写是做了一个类型检查。如果有了这行,假设结构体就没有member这个成员,那么编译会报错。

所以这样写保证了member确实是type的一个成员。

还有,这样写也保证了ptr确实是这个成员类型的指针,如果不是,编译也会报错。再做个实验。

把刚才的代码改一下

struct student *p = container_of((int *)&stu.age, struct student, age);

故意把&stu.age转换成int*,编译就会报警告:

test.c:33:22: warning: incompatible pointer types initializing 'const typeof (((struct student *)0)->age)

      *' (aka 'const unsigned char *') with an expression of type 'int *' [-Wincompatible-pointer-types]

struct student *p = container_of((int *)&stu.age, struct student, age);

                            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

test.c:11:39: note:expanded from macro 'container_of'

const typeof( ((type *)0)->member ) *__mptr = (ptr);    \

                                             ^        ~~~~~


如果没有这一行,那么就不会报错。

所以,作者的出发意图是千方百计地让程序员写出安全的代码啊!真的是用心良苦。

(完)

container_of 和 offsetof 宏详解相关推荐

  1. Offsetof宏详解

    C语言面试的时候可能会考,这样的宏定义: #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER) 函数作用:计算结构体 ...

  2. C 和 C++ 宏 详解

    From:https://www.cnblogs.com/njczy2010/p/5773061.html C中的预编译宏详解:http://www.cppblog.com/bellgrade/arc ...

  3. 【百度联盟峰会】李彦宏详解AI时代思维方式,算法驱动的降维攻击

     [百度联盟峰会]李彦宏详解AI时代思维方式,算法驱动的降维攻击 新智元 2017-05-23 14:51:52 李彦宏 手机 百度 阅读(20879) 评论(19) 新智元报道 来源:百度 [新 ...

  4. Orange's:一个操作系统的实现 Descriptor 3宏详解

    补充:关于GDT/LDT.段选择子和段描述符的解释       GDT/LDT:GDT/LDT是段描述符表,里面定义了每个段的段描述符的界限和属性,而段描述符的基址是在代码段中初始化的.        ...

  5. linux串口通信参数宏详解实例

    详解linux下的串口通讯开发 串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用.常用的串口是RS-232-C接口(又称EIA RS-232-C)它是在1970年由美国电子工业协会 ...

  6. CTL_CODE 宏 详解

    CTL_CODE宏 CTL_CODE:用于创建一个唯一的32位系统I/O控制代码,这个控制代码包括4部分组成: DeviceType(设备类型,高16位(16-31位)), Function(功能2- ...

  7. 【从零开始学习 UVM】6.4、UVM 激励产生 —— uvm_do 宏详解

    请注意,start方法的call_pre_post字段设置为0,这意味着在使用这些序列宏时,序列的pre_body和post_body方法将永远不会被调用.否则,执行流程与通过start方法执行序列时 ...

  8. ZeroMemory宏详解

    在使用C/C++编程时,当我们要清空一个数据的缓冲区的时候我们会执行下面的语句: #define BUF_SIZE 64 // 缓冲区的大小. char buf[BUF_SIZE]; // 数据的缓冲 ...

  9. container_of详解

    container_of详解 #define container_of(ptr, type, member) ({                      \ const typeof(((type ...

最新文章

  1. Java杂记3—流程控制之条件
  2. 2020年日历_2020年《故宫日历》发布 纪念紫禁城建成六百年
  3. 常用binlog日志操作命令
  4. Spring Initializr创建项目,利用阿里云URL解决Initialization failed for https://start.spring.io Please check URL
  5. cocos2dx游戏--欢欢英雄传说--添加攻击按钮
  6. 太极图python自定义函数绘制_[宜配屋]听图阁
  7. Linux学习第五篇之文件处理命令touch、cat、tac、more、less、head、tail
  8. idea 查看jsp是否被引用_IDEA集成Java性能分析神器JProfiler
  9. linux学习作业-第八周
  10. Matlab 实现低通/高通/带通滤波器
  11. html显示emoji表情,在web页面显示emoji表情
  12. 使用 Python 开发 QGIS 插件
  13. 任务管理三部曲 - 模板使用说明(超实用模板下载)
  14. 《TensorFlow技术解析与实战》——3.3 可视化的例子
  15. Illegal character
  16. 计算机wps文字基础知识,计算机基础及WPS Office应用常见考试内容
  17. win7 eclipse调用虚拟机ubuntu部署的hadoop2.2.0伪分布(1)
  18. canvas--放大镜效果
  19. 企业转型升级,务必抓住“企业上云”政策红利
  20. java后台图片大小压缩

热门文章

  1. Golang之gjson
  2. 类选择器与ID选择器的比较
  3. [JZOJ4788] 【NOIP2016提高A组模拟9.17】序列
  4. NanoProfiler-Step1翻译
  5. Compiling Cpp(zz)
  6. 2、SharePoint安装篇——之安装Microsoft Office SharePoint Server 2007
  7. Golang 入门笔记(一)
  8. CUDA占用率计算方法
  9. matlab图像处理——平滑滤波
  10. 科大星云诗社动态20220110