Linux C Programming Skills - awokezhou/LinuxPage GitHub Wiki
在编程时,常常会遇到两个数据结构有继承、父子关系或者概念包含的场景,例如以下情况
-
USB设备中存在主机控制器、接口、端点的概念,一个主机控制器有多个接口,一个接口有多个端点,它们是概念包含,指针层级关系为
hcd->interface->endpoint
,如果在一个函数作用域中,只有接口的指针而没有主机控制器的指针,或者只有端点的指针,没有接口的指针,想要获取它们的上层概念指针,怎么办? -
网络子系统中,系统为网络接口抽象了
net_device
结构,但是每个设备都有其私有的结构private,net_device->private = private,如何通过private访问到net_device? -
在HTPT Server编程中,连接包含会话,会话包含请求,connection->session->request,如何在只有session的情况下获取connection,或者只有request的情况下获取session?
要处理以上问题,核心是container_of
宏定义,定义如下
#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) );})
这个宏定义传入三个参数,prt、type、member,它做了什么事情呢,直观的解释看下图,
两个结构体,一个是father,一个是child,father包含child,container_of宏的三个参数,ptr是一个指针,指向child,type是father的类型,member是child在father结构体中的成员名称。container_of宏定义了一个type类型的指针,计算child到father的偏移量,然后将指针以这个偏移量移动,就指向了father。
下面以一个例子说明这个宏定义怎么使用。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct _student
{
int age;
int class;
char name[32];
};
typedef struct _student student;
#define age_to_student(p)\
container_of(p, student, age)
int main(int argc, char *argv[])
{
int *p_age;
student *p_std;
student std_test;
std_test.age = 15;
std_test.class = 1;
sprintf(std_test.name, "xiao ming");
p_age = &std_test.age;
p_std = age_to_student(p_age);
printf("p_std name %s\n", p_std->name);
return 0;
}
student结构体有三个成员,age、class和name,使用一个指针p_age指向age,container_of宏的ptr参数传入p_age,第二个参数type为student,第三个参数member为age,宏返回的就是student的头地址。
上一个例子比较简单,不是linux中使用比较普遍的情况,更加常见的是两个结构体互相关联,并且是通过指针进行关联的
struct _structa {
char name[NAME_SIZE];
struct _structb *b;
int number;
};
typedef struct _structa structa;
struct _structb {
char name[NAME_SIZE];
struct _structa *a;
int number;
};
typedef struct _structb structb;
#define structa_to_structb(ptr) \
container_of(ptr, struct _structb, a)
void structb_to_structa_test()
{
structa *a;
structb *b;
structa *ptr;
structb *test;
a = mem_alloc_z(sizeof(structa));
b = mem_alloc_z(sizeof(structb));
sprintf(a->name, "this is a");
sprintf(b->name, "this is b");
a->b = b;
b->a = a;
ptr = &b->a;
printf("a 0x%x\n", a);
printf("&a 0x%x\n", &a);
printf("b 0x%x\n", b);
printf("&b 0x%x\n", &b);
printf("a->b 0x%x\n", a->b);
printf("&a->b 0x%x\n", &a->b);
printf("b->a 0x%x\n", b->a);
printf("&b->a 0x%x\n", &b->a);
test = structa_to_structb(ptr);
printf("test name %s\n", test->name);
}
两个结构体structa和structb,各有一个指针指向另一个结构体,这里需要注意一点的是,ptr = &b->a而不是ptr = b->a,因为container_of宏定义的使用,指针指向的地址必须是结构体本身的。 ptr = b->a,这个指针指向的是结构体a,但是这个指针本身的地址是另外一个地址,ptr = &b->a,则ptr的地址在结构体b中,打印的结果是
a的值0x223c010
b的值0x223c050
&a的地址0xfde458b0
&b的地址0xfde458ab
a->b的值0x223c050
b->a的值0x223c010
&a->b的值0x223c030
&b->a的值0x223c070