typeof、offsetof、container_of的解释

链表是内核最经典的数据结构之一,说到链表就不得不提及内核最经典(没有之一)的宏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

时间: 2024-10-13 10:35:54

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

typeof, offsetof 和container_of

要理解Linux中实现的双向循环链表("侵入式"链表),首先得弄明白宏container_of. 本文尝试从gcc的关键字typeof和宏offsetof入手,循序渐进地剖析宏container_of之实现原理. 1. typeof (from: https://en.wikipedia.org/wiki/Typeof) typeof is an operator provided by several programming languages to determine the da

container_of()详解

?原地址:http://radek.io/2012/11/10/magical-container_of-macro/ 当你开始内核编程的时候,你会随便看看代码,那么你可能很快就会碰到这个神奇的预处理代码结构. 它是用来干嘛的?正如它的名称那样,它获取的是它的容器的地址.该函数需要三个变量--指针,容器的类型,指向成员的指针.(译者注:最终返回的是,该成员所处的结构体的指针.知道成员的指针,根据结构体的size就可以算出来了,具体的算法还要看下面)该宏将返回包含对应成员的容器的指针地址.它确实是

Linux内核数据结构——链表

目录 目录 简介 单向链表 双向链表 环形链表 Linux内核中的链表实现 offsetof container_of container_of 第一部分 container_of 第二部分 链表初始化 向链表中增加一个节点 删除节点 移动节点 判断链表是否为空 遍历链表 Demo测试 mlisth mlistc 执行结果 简介 最近在学习Android Binder驱动程序实现的时候,发现里面的数据结构用到了struct list_head.而我google发现struct list_head

侵入式单链表的简单实现

通常情况下,单链表的定义是这样子滴, typedef struct foo_s { int data; struct foo_s *next; } foo_t; 结构体里包含了链表指针next; 而侵入式单链表却不同,让结构体包含一个通用的链表.看起来是这个样儿滴, typedef struct list_s { struct list_s *next; } list_t; typedef struct foo_s { int data; list_t link; } foo_t; 所有包含了l

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

对于巩固复习js更是大有裨益.    初级Javascript: 1.JavaScript是一门什么样的语言,它有哪些特点? 没有标准答案. 2.JavaScript的数据类型都有什么? 基本数据类型:String,Boolean,Number,Undefined, Null 引用数据类型:Object(Array,Date,RegExp,Function) 那么问题来了,如何判断某变量是否为数组数据类型? 方法一.判断其是否具有“数组性质”,如slice()方法.可自己给该变量定义slice方

typeof、offsetof、container_of

typeof 用于获取一个对象的类型,比如: unsigned int a = 1; // typeof (a) is unsigned int short b = 2; // typeof (b) is short offsetof #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER) 获取某个成员变量(MEMBER)在其所在的结构体(TYPE)里的偏移: 首先将地址0强行转换为 TYPE * 类型 再将此对象中的MEM

对offsetof、 container_of宏和结构体的理解

offsetof 宏 #include<stdio.h> #define offsetoff(type, member)      ((int)&((type*)0)->member) /* ((type*)0)->member 释义:声明一个相应类型的结构体指针,该指针指向0地址处.再通过该指针访问各元素.我们只要获取一个指针就能访问其内部的元素吗,可以这么搞?其实我是想联系全局和非全局变量,通过上面这个代码也许你不明白我要表达的意思.请继续慢慢看,直到本文后面,结合本文

C语言笔记(结构体与offsetof、container_of之前的关系)

关于结构体学习,需要了解:结构体的定义和使用.内存对齐.结构体指针.得到结构体元素的偏移量(offsetof宏实现) 一.复习结构体的基本定义和使用 1 typedef struct mystruct 2 { 3 int a; 4 char b; 5 double c; 6 }MyS1; 7 8 /* 9 函数功能:演示结构体的定义和使用 10 */ 11 void func1(void) 12 { 13 //定义时赋值 14 MyS1 s1 = { 15 .a =1, 16 .b =2, 17

C语言之offsetof宏和container_of宏

首先我们要明白一点通过结构体变量来访问结构体中的各个元素时,其本质上是 通过指针的方式来实现访问的,只不过是这个时候编译器帮自动帮我们计算了每个 元素与结构体起始地址之间的偏移量而已 一:offsetof宏: #define offsetof(TYPE, MEMBER) ((int) &((TYPE *)0)->MEMBER) 1:参数与返回值分析: (1)TYPE是结构体类型,MEMBER是结构体中一个元素的元素名 (2)这个宏返回的是member元素相对于整个结构体变量的首地址的偏移量,