container_of宏分析

1. 背景概要

最近这几天在忙于研究spi驱动,在看spi核心代码(spi.c)时,发现一个container_of宏,不甚了解,于是深入研究之。这不看不知道,一看吓一跳啊,一跟进去又给你整个更复杂的宏。好的,废话不多说,直接上代码:

/* kernel3.0.15/drivers/spi/spi.c */

/* kernel3.0.15/include/linux/spi/spi.h */


/*kernel3.0.15/include/linux/kernel.h*/


一路追踪,终于到了container_of宏了,下面慢慢给你道清其原委。

2. container_of宏分析

(1)container_of在Linux内核中是一个常用的宏,用于从包含在某个结构中的指针获得结构本身的指针,通俗地讲就是通过结构体变量中某个成员的首地址进而获得整个结构体变量的首地址。

(2)container_of宏参数说明

ptr:表示结构体中成员的地址;

type:表示结构体类型;

member:表示结构体中的成员;

(3)container_of宏实现

其实它的语法很简单,只是一些指针的灵活应用,它分两步:
第一步,首先定义一个临时的数据类型(通过typeof( ((type *)0)->member )获得)与ptr相同的指针变量__mptr,然后用它来保存ptr的值。
说明:typeof是GNU C对标准C的扩展,它的作用是根据变量获取变量的类型,typeof关键字在linux 内核中很常见。
第二步,用(char *)__mptr减去member在结构体中的偏移量,得到的值就是整个结构体变量的首地址(整个宏的返回值就是这个首地址)。

注:offset宏分析见下文。

3. container_of宏使用示例

①正确示例

#include <stdio.h>

#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) );})
struct test_struct {
	int num;
	char ch;
	float f1;
};

int main(void)
{
	struct test_struct *test_struct;
	struct test_struct init_struct = {12,'a',12.3};
	char *ptr_ch = &init_struct.ch;
	
	test_struct = container_of(ptr_ch,struct test_struct,ch);
	printf("test_struct->num = %d\n",test_struct->num);
	printf("test_struct->ch = %c\n",test_struct->ch);
	printf("test_struct->ch = %f\n",test_struct->f1);
	
	return 0;
}

/**
 * 运行结果:
 * test_struct->num = 12
 * test_struct->ch = a
 * test_struct->ch = 12.300000
 */

②错误示例

#include <stdio.h> 

#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) );}) 

struct test_struct {
	int num;
	char ch;
	float f1;
}; 

int main(void)
{
	struct test_struct *test_struct;
	char real_ch = 'A';
	char *ptr_ch = &real_ch;
	
	test_struct = container_of(ptr_ch,struct test_struct,ch);
	printf("test_struct->num = %d\n",test_struct->num);
	printf("test_struct->ch = %c\n",test_struct->ch);
	printf("test_struct->ch = %f\n",test_struct->f1);
    
	return 0;
}

/**
 * 运行结果:
 * test_struct->num = 0
 * test_struct->ch = A
 * test_struct->ch = 0.000000
 * 
 * 注意,由于这里并没有使用一个具体的结构体变量,所以成员num和f1的值是不确定的。
 */

4. offset宏分析

(1)offset宏实现

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)
对这个宏的分析大致可以分为以下4步进行讲解:
①( (TYPE *)0 ) 0地址强制 "转换" 为 TYPE结构类型的指针;
②((TYPE *)0)->MEMBER 访问TYPE结构中的MEMBER数据成员;
③&( ( (TYPE *)0 )->MEMBER)取出TYPE结构中的数据成员MEMBER的地址;
④(size_t)(&(((TYPE*)0)->MEMBER))结果转换为size_t类型。
宏offsetof的巧妙之处在于将0地址强制转换为 TYPE结构类型的指针,TYPE结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址。可能有的读者会想是不是非要用0呢?当然不是,我们仅仅是为了计算的简便。也可以使用是他的值,只是算出来的结果还要再减去该数值才是偏移地址。

(2)offset宏使用示例

#include<stdio.h>

#defineoffsetof(TYPE, MEMBER) ((size_t) &((TYPE *)4)->MEMBER)

struct test_struct {
	int num;
	char ch;
	float f1;
};

int main(void)
{
	printf("offsetof (struct test_struct,num) = %d\n",offsetof(struct test_struct,num)-4);
	printf("offsetof (structtest_struct,ch) = %d\n",offsetof(struct test_struct,ch)-4);
	printf("offsetof (struct test_struct,f1) = %d\n",offsetof(struct test_struct,f1)-4);

	return 0;
}

/**
 * 运行结果:
 * offsetof (struct test_struct,num) = 0
 * offsetof (struct test_struct,ch) = 4
 * offsetof (struct test_struct,f1) = 8
 *
 * 为了让大家加深印象,我们在代码中没有使用0,而是使用的4,
 * 所以在最终计算出的结果部分减去了一个4才是偏移地址,当然实际使用中我们都是用的是0。
 * 注:代码中开始宏定义时4改为0,在打印时就不用减去4了。
 */


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章