如何从Linux内核代码中学习获得结构体成员偏移量

C/C++(70)
最近在看Linux内核设计与实现这本书,对于结构体初始化方式比较好奇,以前用C写单片机接触的比较少;百度后看到如下解释: 在阅读GNU/Linux内核代码时,我们会遇到一种特殊的结构初
最近在看内核设计与实现这本书,对于结构体初始化方式比较好奇,以前用C写单片机接触的比较少;百度后看到如下解释:
在阅读GNU/Linux内核代码时,我们会遇到一种特殊的结构初始化方式。该方式是某些C教材(如谭二版、K&R二版)中没有介绍过的。这种方式称为指定初始化(designated initializer)。
下面我们看一个例子,Linux-2.6.x/drivers/usb/storage/usb.c中有这样一个结构体初始化项目:&
static struct usb_driver usb_storage_driver = {&
.owner = THIS_MODULE,&
.name = &usb-storage&,&
.probe = storage_probe,&
.disconnect = storage_disconnect,&
.id_table = storage_usb_ids, };&
乍一看,这与我们之前学过的结构体初始化差距甚远。其实这就是前面所说的指定初始化在Linux设备驱动程序中的一个应用,它源自ISO C99标准。以下我摘录了C Primer Plus第五版中相关章节的内容,从而就可以很好的理解2.6版内核采用这种方式的优势就在于由此初始化不必严格按照定义时的顺序。这带来了极大的灵活性,其更大的益处还有待大家在开发中结合自身的应用慢慢体会。
已知一个结构,定义如下
struct book {&
char title[MAXTITL];
char author[MAXAUTL];&
C99支持结构的指定初始化项目,其语法与数组的指定初始化项目近似。只是,结构的指定初始化项目使用点运算符和成员名(而不是方括号和索引值)来标识具体的元素。例如,只初始化book结构的成员value,可以这样做: struct book surprise = { .value = /yawang/p/10.99 };&可以按照任意的顺序使用指定初始化项目:
struct book gift = { .value = /yawang/p/25.99, .author =&James Broadfool&, .title = &Rue for the Toad&};&正像数组一样,跟在一个指定初始化项目之后的常规初始化项目为跟在指定成员后的成员提供了初始值。另外,对特定成员的最后一次赋值是它实际获得的值。例如,考虑下列声明:
struct book gift = { .value = /yawang/p/18.90, .author =&Philionna pestle&, 0.25}; 这将把&#赋给成员value,因为它在结构声明中紧跟在author成员之后。新的&#代替了早先的赋&#。 有关designated initializer的进一步信息可以参考c99标准的6.7.8节Ininialization。
  特定的初始化
  标准C89需要初始化语句的元素以固定的顺序出现,和被初始化的数组或结构体中的元素顺序一样。在ISO C99中,你可以按任何顺序给出这些元素,指明它们对应的数组的下标或结构体的成员名,并且GNU C也把这作为C89模式下的一个扩展。这个扩展没有在GNU C++中实现。为了指定一个数组下标,在元素值的前面写上“[index] =”。比如: int a[6] = { [4] = 29, [2]
  相当于: int a[6] = { 0, 0, 15, 0, 29, 0 };
  下标值必须是常量表达式,即使被初始化的数组是自动的。一个可替代这的语法是在元素值前面写上“.[index]”,没有“=”,但从GCC 2.5开始就不再被使用,但GCC仍然接受。 为了把一系列的元素初始化为相同的值,写为“[first ... last] = value”。这是一个GNU扩展。比如: int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
  如果其中的值有副作用,这个副作用将只发生一次,而不是范围内的每次初始化一次。注意,数组的长度是指定的最大值加一。在结构体的初始化语句中,在元素值的前面用“.fieldname = ”指定要初始化的成员名。例如,给定下面的结构体, struct point { int x, };
  和下面的初始化, struct point p = { .y = yvalue, .x = xvalue };
  等价于: struct point p = { xvalue, yvalue };
  另一有相同含义的语法是“.fieldname:”,不过从GCC 2.5开始废除了,就像这里所示: struct point p = { y: yvalue, x: xvalue };
  “[index]”或“.fieldname”就是指示符。在初始化共同体时,你也可以使用一个指示符(或不再使用的冒号语法),来指定共同体的哪个元素应该使用。比如: union foo { }; union foo f = { .d = 4 };
  将会使用第二个元素把4转换成一个double类型来在共同体存放。相反,把4转换成union foo类型将会把它作为整数i存入共同体,既然它是一个整数。(参考5.24节向共同体类型转换。)你可以把这种命名元素的技术和连续元素的普通C初始化结合起来。每个没有指示符的初始化元素应用于数组或结构体中的下一个连续的元素。比如, int a[6] = { [1] = v1, v2, [4] = v4 };
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:331903次
积分:4248
积分:4248
排名:第6171名
转载:568篇
(2)(1)(5)(11)(31)(1)(13)(14)(1)(27)(16)(14)(3)(46)(29)(35)(23)(58)(9)(7)(34)(2)(8)(6)(1)(2)(2)(28)(33)(71)(39)(2)Linux(41)
在Linux内核中,提供了一个用来创建双向循环链表的结构 list_head。虽然linux内核是用C语言写的,但是list_head的引入,使得内核数据结构也可以拥有面向对象的特性,通过使用操作list_head 的通用接口很容易实现代码的重用,有点类似于C++的继承机制(希望有机会写篇文章研究一下C语言的面向对象机制)。下面就是kernel中的list_head结构定义:struct list_head {  struct list_head *next, *};#define LIST_HEAD_INIT(name) { &(name), &(name) }需要注意的一点是,头结点head是不使用的,这点需要注意。使用list_head组织的链表的结构如下图所示:&list_head这个结构看起来怪怪的,它竟没有数据域!所以看到这个结构的人第一反应就是我们怎么访问数据?其实list_head不是拿来单独用的,它一般被嵌到其它结构中,如:struct file_node{    struct list_};此时list_head就作为它的父结构中的一个成员了,当我们知道list_head的地址(指针)时,我们可以通过list.c提供的宏 list_entry 来获得它的父结构的地址。下面我们来看看list_entry的实现:#define list_entry(ptr,type,member)\  container_of(ptr,type,member)&#define offsetof(TYPE,MEMBER) ((size_t)&((TYPE *)0)-&MEMBER)#define container_of(ptr,type,member) ( {\  const typeof( ((type*)0)-&member ) *__mptr=(ptr);\  (type*)( (char*)__mptr - offsetof(type,member) );} )&这里涉及到三个宏,还是有点复杂的,我们一个一个来看:#define offsetof(TYPE,MEMBER) ( (size_t)& ((TYPE *)0)-& MEMBER )我们知道 0 地址内容是不能访问的,但 0地址的地址我们还是可以访问的, 这里用到一个取址运算符(TYPE *)0 它表示将 0地址强制转换为TYPE类型,((TYPE *)0)-& MEMBER 也就是从0址址找到TYPE 的成员MEMBER 。我们结合上面的结构来看struct file_node{    struct list_};将实参代入 offset( struct file_node, node );最终将变成这样:( (size_t) & ((struct file_node*)0)-& node );这样看的还是不很清楚,我们再变变:struct file_node *p = NULL;& p-&这样应该比较清楚了,即求 p 的成员 node的地址,只不过p 为0地址,从0地址开始算成员node的地址,也就是 成员 node 在结构体 struct file_node中的偏移量。offset宏就是算MEMBER在TYPE中的偏移量的。我们再看第二个宏#define container_of(ptr,type,member) ( {\  const typeof( ((type*)0)-&member ) *__mptr=(ptr);\  (type*)( (char*)__mptr - offsetof(type,member) );} )这个宏是由两个语句组成,最后container_of返回的结果就是第二个表达式的值。这里__mptr为中间变量,这就是list_head指针类型,它被初始化为ptr的值,而ptr就是当前所求的结构体中list_head节点的地址。为什么要用中间变量,这是考虑到安全性因素,如果传进来一个ptr++,所有ptr++放在一个表达式中会有副作用,像 (p++)+(p++)之类。(char*)__mptr 之所以要强制类型转化为char是因为地址是以字节为单位的,而char的长度就是一个字节。container_of的值是两个地址相减,刚说了__mptr是结构体中list_head节点的地址,offset宏求的是list_head节点MEMBER在结构体TYPE中的偏移量,那么__mptr减去它所在结构体中的偏移量,就是结构体的地址。所以list_entry(ptr,type,member)宏的功能就是,由结构体成员地址求结构体地址。其中ptr 是所求结构体中list_head成员指针,type是所求结构体类型,member是结构体list_head成员名。通过下图来总结一下:&&继续列举一些双链表的常用操作:双向链表的遍历——list_for_each//注:这里prefetch 是gcc的一个优化选项,也可以不要#define list_for_each(pos, head) \&&&&&&&& for (pos = (head)-& prefetch(pos-&next), pos != (head); \&&&&&&&&&&&&&&&& pos = pos-&next)&生成双向链表的头结点——LIST_HEAD()LIST_HEAD() -- 生成一个名为name的双向链表头节点#define LIST_HEAD(name) \struct list_head name = LIST_HEAD_INIT(name)static inline void INIT_LIST_HEAD(struct list_head *list){  list-&next =  list-&prev =}双向链表的插入操作 --&list_add()将new所代表的结构体插入head所管理的双向链表的头节点head之后: (即插入表头)static inline void list_add(struct list_head *new, struct list_head *head){  __list_add(new, head, head-&next);}static inline void __list_add( struct list_head *new,&struct list_head *prev,&struct list_head *next){  next-&prev =  new-&next =  new-&prev =  prev-&next =}从list中删除结点——list_del()static inline void list_del(struct list_head *entry){  __list_del(entry-&prev, entry-&next);  entry-&next = LIST_POISON1;  entry-&prev = LIST_POISON2;}static inline void __list_del(struct list_head * prev, struct list_head * next){  next-&prev =  prev-&next =}&判断链表是否为空(如果双向链表head为空则返回真,否则为假)——list_empty()static inline int list_empty(const struct list_head *head){  return head-&next ==}&
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:424302次
积分:8118
积分:8118
排名:第2098名
原创:352篇
转载:55篇
评论:120条
(2)(14)(4)(2)(8)(4)(2)(6)(5)(6)(4)(18)(12)(7)(2)(10)(10)(4)(13)(25)(22)(10)(11)(21)(4)(7)(12)(28)(57)(27)(7)(14)(23)(7)(7)(1)君,已阅读到文档的结尾了呢~~
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
Linux内核代码 结构体初始化
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='/DocinViewer--144.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口}

我要回帖

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信