在系统的头文件中,有很多函数是用宏定义的方式定义的,在list.h中,对双向链表进行遍历的函数都是通过这种方式定义的,其中有许多很巧妙的地方,也是很有意思的,短小精干。下面接着上一篇文章的内容继续分析一下list.h。

      首先是下面这个宏定义:

 

大致一看,就知道这个宏是调用container_of的,我们再看一下它是如何定义的:

 

看着很复杂,我们先大致扫描一下,发现其中的offsetof,它的宏定义如下:

 

好了,到此对于我们要分析的第一个宏的所有有关宏都找出来了,我们再逆推上去,分析它的作用。最后一个宏,是先把0强制类型转换成为指向一个TYPE型数据的指针,然后取其中的MEMBER成员,再取它的地址,最后再强制类型转换成size_t型,就是unsigned long型,也就是MEMBER成员的地址,而该TYPE型数据的首地址为0,所以这个地址也就是MEMBER成员的偏移量,所以offsetof(TYPE, MEMBER)得到的是TYPE类型数据中成员MEMBER的地址偏移量。然后我们再一起看上一个宏,第一句话:const typeof( ((type *)0)->member ) *__mptr = (ptr) ,看等号左边,先把0强制类型转换成为指向一个type型数据空间的指针,然后取其中的成员member,然后通过typeof得到这个成员member的类型,然后定义一个指向这个类型数据的指针命名为__mptr,并赋值为ptr;再看第二句话:(type *)( (char *)__mptr - offsetof(type,member) ) ,把上面得到的__mptr减去成员member的偏移量,然后在强制类型转换成指向type型空间的指针,也就是地址,也就是得到了type类型的首地址,也就得到了一个指针,可以用它做“->”运算,得到其结构体中的任意一个值。好了,这个宏说完了,总结一句话,它的作用就是得到这个type类型的结构体的指针,然后通过它获取其中的元素,它这样定义的原因可能是为了增加极高的可移植性吧,反正它基本包括了所有种可能。

      第一个总算分析完了,开始看第二个,内容如下:

 

一看就轻松一点了,因为它正是调用了第一个宏来定义的,就是找到ptr->next的地址,也就是第一节点的地址,所以它叫list_first_entry。

      迫不及待的来看下几个:

 

这两个宏有两点不同,1、名字不同,第二个要多两个下划线(--!),2、在for循环的条件中第二个要少一个prefetch(pos->next)。先看内容较少的那个,循环的初值为pos=(head)->next,就是指向第一个结点,循环体是不断地向后指,循环条件是pos不是head位置,很典型的便利链表,就不多说了。对于prefetch,它的作用是预取结点,以提高速率的。这样就明白了为什么要定义两个了,因为一个适合元素较多的链表,而后者适合元素较少的。

      再往下看:

 

和第一个好像啊,只是第一个是取head的next,这是取prev,而循环体也是不断向前指,很明显了,这是倒着遍历链表呢。

      下面的内容如下:

 

还是很眼熟,要比上面见到的那些名字多个safe,那么他们安全在哪里呢,就是多定义了一个n,并赋值为pos->next或pos->prev,然后在循环体中用n作为暂存容器,进行向前指或向后指,以防止意外发生。。。(想的确实很多啊)

      再向下看,又有我们更为熟悉的东西:

 

只用看第一个定义,循环初值为pos=list_entry((head)->next,typeof(*pos),member),通过上面的分析,套入这些参数,就可以知道,就是把pos指向了head->next,就是第一个结点,循环条件中的pos->member!=(head),可以理解成member就是类型为结构体list_head的一个数据单元,当这个单元就是head时,说明循环了一圈,循环体为pos=list_entry(pos->member.next,typeof(*pos),member),就是把pos的成员member的next赋给pos,相当于把pos指向了下一个结点,使循环“启动起来”。它这样写是因为结构体list_head只是一个“桥”,它一般不会单独使用的,而是放在其他结构体中嵌套使用,所以这时它就成为一个结构体中的成员member。第二个宏定义顾名思义,就是倒着遍历。

      再往下看:

 

它们的作用是从当前位置的下一个结点(前一个结点)向后(向前)遍历至链表尾部(首部),只要看好它调用list_entry时使用的参数就很好理解了。

      后面的宏也是一样的理解方式:

 

但是它没有给循环赋初值,那就意思是从当前位置包括当前结点开始,向后遍历。

      再往后面看了几个函数,其实没有仔细看,因为从名字就知道了:

 

名字和前面的好多宏都是只差一个safe,那就是说是安全一点的遍历,也就是加了一个中间暂存的变量。

      至此,list.h后面的宏定义方式定义的一些函数也分析完了,其实和上一篇类似,只要抓住一个最基本最主要的定义,下面的都是调用它来实现的,这篇文章的基本定义就是那个开头分析的list_entry(ptr,type,member),只要理解了它,后面的也都迎刃而解了。通过分析这个list.h,感触颇多,最多的还是感觉对函数的可移植性的操作,实在是太经典了。

      好了,在list.h后面还有对哈希链表的操作,后面有时间再看一看。依然老话,大家多多指正!

Logo

更多推荐