在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. CTL_CODE 宏 详解

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

  2. 预定义宏,C语言预定义的宏详解

    1.预定义宏 对于预定义宏,相信大家并不陌生.为了方便处理一些有用的信息,预处理器定义了一些预处理标识符,也就是预定义宏.预定义宏的名称都是以"__"(两条下划线)开头和结尾的,如 ...

  3. (转)C/C++ 宏详解

    众多C++书籍都忠告我们C语言宏是万恶之首,但事情总不如我们想象的那么坏,就如同goto一样.宏有一个很大的作用,就是自动为我们产生代码.如果说模板可以为我们产生各种型别的代码(型别替换),那么宏其实 ...

  4. C++宏定义详解

    一.#define的基本用法     #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率,但学生在学习时往往不能 理解该命令的本质 ...

  5. [转]C++宏定义详解

    一.#define的基本用法     #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率,但学生在学习时往往不能 理解该命令的本质 ...

  6. 【转载】C++宏定义详解

    摘自:http://blog.chinaunix.net/uid-21372424-id-119797.html   C++宏定义详解 2011-02-14 23:33:24   分类: C/C++ ...

  7. offsetof宏与container_of宏

    offsetof宏与container_of宏1.由结构体指针进而访问各元素的原理(1)通过结构体整体变量来访问其中各个元素,本质上是通过指针方式来访问的,形式上是通过.的方式来访问的(这个时候其实是 ...

  8. [C++] C++中的宏定义详解

    转载自:C++中的宏定义 和 C++宏定义详解 一.#define解析     #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率 ...

  9. [转帖]基于VIM漏洞CVE-2019-12735的VIM宏后门病毒详解

    基于VIM漏洞CVE-2019-12735的VIM宏后门病毒详解 不明觉厉 只要是人做的东西 就会有bug 就会有安全问题 就看发现bug 或者是发现安全问题 有没有收益了 会用linux的都是比较熟 ...

随机推荐

  1. GridView里的按钮事件

    http://www.cnblogs.com/insus/archive/2012/09/22/2697862.html using System;using System.Collections.G ...

  2. 在ubuntu 14.04 编译android 2.3.1 错误解决办法

    首先必须降低gcc版本: sudo apt-get install gcc-4.4sudo apt-get install g++-4.4sudo rm -rf /usr/bin/gcc /usr/b ...

  3. 最全PyCharm教程--for python

    PyCharm简介: PyCharm是由JetBrains打造的一款Python IDE,VS2010的重构插件Resharper就是出自JetBrains之手.   同时支持Google App E ...

  4. java24种设计模式

    一.设计模式定义 设计模式(Design Pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结,使用设计模式是为了可重用代码.让代码更容易被他人理解并且保证代码可靠性. ...

  5. 全新优化解决方案:Amplify Impostors

    https://mp.weixin.qq.com/s/q6WPDHhMFk0jAMx937MLFg

  6. HDU - 1754 I Hate It (线段树单点修改,求区间最大值)

    很多学校流行一种比较的习惯.老师们很喜欢询问,从某某到某某当中,分数最高的是多少. 这让很多学生很反感. 不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问.当然,老师有 ...

  7. fseek函数

    函数名:fseek函数 头文件:#include<stdio.h> 功能:把与fp有关的文件位置指针放到一个指定位置. 格式:  int fseek(FILE *stream, long ...

  8. ASP.NET-GridView之固定表数据滚动

    有时候,在线Web开发时,需要显示的数据往往会超过我们规定的表格长度,所以为了方便显示大量数据,为了美观,这里提出了两种显示数据方式. ①可以滚动显示数据但是表头未能获取 效果显示 前端DEMO &l ...

  9. Node JS爬虫:爬取瀑布流网页高清图

    原文链接:Node JS爬虫:爬取瀑布流网页高清图 静态为主的网页往往用get方法就能获取页面所有内容.动态网页即异步请求数据的网页则需要用浏览器加载完成后再进行抓取.本文介绍了如何连续爬取瀑布流网页 ...

  10. Div的移动

    //JQuery 拖拽本体DIV,把以下代码全部复制即可 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&q ...