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

要处理以上问题,核心是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

⚠️ **GitHub.com Fallback** ⚠️