根据结构体成员变量的地址得到结构体起始地址

本文参考《Linux操作系统原理与应用》以及如下博客:
https://blog.csdn.net/this_capslock/article/details/39189947

给定一个结构体定义type,这个结构体中某个成员变量的名字member以及它的地址ptr,如何得到包含此成员变量的结构体的地址?即通过结构体成员的地址获取结构体变量的地址?
为了便于分析,我们给出一个实例来说明

struct father_t {
    int a;
    char *b;
    double c;
}f;

根据C语言对struct类型的存储特性,我们可以画这么一个图示:
这里写图片描述

通过分析图示,我们可以看出,我们只需要把当前知道的成员变量的地址ptr,减去它在结构体当中相对偏移4就的到了结构体的地址(ptr-4)。

一、list_entry宏分析
#define list_entry(ptr, type, member) \
    ((type *)( (char *)(ptr) - (unsigned long)(&((type*)0)->member))) 

如下图,指针ptr指向结构体type中的成员member;通过指针ptr,返回结构体type的起始地址,也就是list_entry返回指向type类型的指针。
这里写图片描述

分析:

  • ((type *)0)->member把0地址转换为type结构的指针,然后获取该结构中member域的指针,也就是获得了member在type结构中的偏移量
  • (char *)(ptr) 求出的是ptr的绝对地址
  • 二者相减,于是得到type类型结构体的起始地址
二、typeof学习

typeof关键字是C语言中的一个新扩展,这个特性在linux内核中应用非常广泛。

typeof 是自动推导后面 ( ) 里的数据类型,所以 typeof(int *) 直接推导出了 int * 型

这样的话,当遇到一个非常复杂的表达式我们很难推断其类型的时候,typeof 就很有用了。另外有一点要注意:typeof 是 GNU C 标准里特有的扩展,标准的 ISO C 并没有这个关键字,所以在编译的时候不能加任何 ISO 的 C 标准选项( -std=c90 ),否则会报错,此时把 -std=c90 改成 -std=gnu90 即 GNU 的标准即可。

三、代码

首先看看Linus的实现:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)  
/**
 * container_of - 通过结构体的一个成员获取容器结构体的指针
 * @ptr: 指向成员的指针。
 * @type: 成员所嵌入的容器结构体类型。
 * @member: 结构体中的成员名。
 */
#define container_of(ptr, type, member) ({          
        const typeof( ((type *)0)->member ) *__mptr = (ptr); 
        (type *)( (char *)__mptr - offsetof(type,member) );
        })  

(1)定义一个中间变量__mptr,它等于提供给宏的参数ptr,也就是指向某个成员的指针。这个中间变量的命名意义是:
“__”代表内部使用,内核编程中常常这么做;“m”代表middle。
为了避免对 ptr及prt指向的内容造成破坏,这里不直接使用 ptr 而要多多加一个__mptr。
(2)把__mptr转换成 char *类型,因为offsetof得到的偏移量是以字节为单位。两者相减得到结构体的起始位置,再强制转 换成type类型。

这个宏的作用,就是通过一个容器(结构体)中某个成员的指针得到指向这个容器(结构体)的指针,简单的说就是通过成员 找容器。

这里代码进行验证:


#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>


#define list_entry(ptr, type, member) \
    ((type *)( (char *)(ptr) - (unsigned long)(&((type*)0)->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) );  \
        })  


struct father_t {
    int a;
    char *b;
    double c;
}f;

int main()
{
    char *ptr = (char *)&(f.b);
    printf("stndard entry address:0x%x\n",&f);
    printf("stndard member address:0x%x\n",&(f.b)); //
    printf("stndard member address:0x%x\n",ptr);


    printf("offset: %d\n",offsetof(struct father_t,b));
    printf("my entry :0x%x\n",list_entry(ptr,struct father_t,b));
    printf("linus entry :0x%x\n",container_of(ptr,struct father_t,b));


    return 0;
} 

这里首先使用了自己编写的list_entry,之后使用了Linux源代码中的container_of,只是Linus的代码在有些gcc环境下编译不通过,在本地环境下测试通过。

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值