• 指针的灵活应用--内核链表中的container_of


      指针在C语言是一种很强大的武器,运用的好的话可以为我们很好的服务,这里我们以内核链表中的一个宏container_of,来分析编写内核代码的大佬们是如何巧妙运用指针的。

      我们先直接给出container_of的定义

     1 /** 
     2 * container_of - cast a member of a structure out to the containing structure 
     3 * @ptr:    the pointer to the member. 
     4 * @type:   the type of the container struct this is embedded in. 
     5 * @member: the name of the member within the struct. 
     6 * */  
     7   
     8 #define container_of(ptr, type, member) ({                    /          
     9         const typeof( ((type *)0)->member ) *__mptr = (ptr); -/  
    10         (type *)( (char *)__mptr - offsetof(type,member) );})

      在container_of中,我们又发现有一个typeof和offsetof,我们先来分别解释一下它们。

    1.offsetof

      offsetof的定义如下:

    1 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

      offsetof的意思是,在求结构体TYPE中,MEMBER相对于TYPE起始地址的偏移量。

      offsetof是0指针的灵活运用。我们可以直接用代码测试。

     1 /*************************************************************************
     2         > File Name: mypointer.c
     3         > Author: yudongqun
     4         > Mail: qq2841015@163.com
     5         > Created Time: Thu 05 Nov 2020 10:20:30 AM CST
     6  ************************************************************************/
     7 #include <stdio.h>
     8 #define NAME_LEN 20
     9 #define offset(type, member) ((size_t)&((type *)(NULL))->member)
    10 typedef struct {
    11     char name[NAME_LEN];
    12     int school_number;
    13 } Students;
    14 
    15 int main(void) {
    16     Students stu;
    17     printf("%lu
    ", offset(Students, school_number));
    18     printf("%lu
    ", (size_t)((void *)&stu.school_number - (void *)&stu));
    19     return 0;
    20 }

    编译并运行:

    ydqun@VM-0-9-ubuntu trash % ./a.out [0]
    20
    20

    可以看到,我们通过offsetof求出了school_number成员在Students结构体中的位置,即school_number相对于Students结构体中的偏移量。

    2.typeof 

      typeof关键字是C语言中的一个新扩展,这个特性在linux内核中应用非常广泛。仔细学习可以访问http://gcc.gnu.org/onlinedocs/gcc/Typeof.html#Typeof。我们这里只讲一下在contianer_of中typeof的用法。首先在#define container_of(ptr, type, member)中,type是结构体的名字,member指结构体type中的成员,ptr指某个type结构体变量中member的地址,我们单独把这语句代码const typeof( ((type *)0)->member ) *__mptr = (ptr),来写一个程序验证一下它的效果。

     1 /*************************************************************************
     2         > File Name: mypointer.c
     3         > Author: yudongqun
     4         > Mail: qq2841015@163.com
     5         > Created Time: Thu 05 Nov 2020 10:20:30 AM CST
     6  ************************************************************************/
     7 #include <stdio.h>
     8 #define NAME_LEN 20
     9 typedef struct {
    10     char name[NAME_LEN];
    11     int school_number;
    12 } Students;
    13 
    14 int main(void) {
    15     Students stu = {
    16         .name = "Tom",
    17         .school_number = 1234
    18     };
    19     typeof(((Students *)0)->school_number)* p = &stu.school_number;
    20     printf("*p = %d, stu.school_number = %d
    ", *p, stu.school_number);
    21     printf("p = %p, &stu.school_number = %p
    ", p, &stu.school_number);
    22     return 0;
    23 }

    编译并运行。

    ydqun@VM-0-9-ubuntu 20201031 % gcc my_typeof.c                                                                    [0]
    ydqun@VM-0-9-ubuntu 20201031 % ./a.out                                                                            [0]
    *p = 1234, stu.school_number = 1234
    p = 0x7ffe0a2e99f4, &stu.school_number = 0x7ffe0a2e99f4

      由测试结果得知,typeof( ((type *)0)->member ) *__mptr = (ptr)的作用就是获取结构体type中member的类型去定义一个指向该member类型的指针mptr,并把指针ptr赋值给mptr,其中ptr是某个type结构体中的变量member的地址。

      至此,我们已经分析完offsetof和typeof,现在可以直接分析container_of了。

    3.container_of

      其实container_of已经没啥好分析的了,const typeof( ((type *)0)->member ) *__mptr = (ptr),这一句代码是把指针ptr赋值给同类型的mptr。而宏定义#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)),是巧妙运用了0指针地址去求成员member在结构体type中的偏移量。那么我么知道成员member的地址ptr和偏移量,直接用member的地址去减去偏移量,就求出member成员所在的结构体变量的地址了,这就是container_of的作用。说到这里,也许你会觉得为啥要多执行const typeof( ((type *)0)->member ) *__mptr = (ptr);这一步操作,其实,这一步是防止开发者使用container_of时,输入参数有误,如果参数ptr输入错误,既ptr指向的类型和member的类型不匹配,编译器就会报错,所以container_of的严谨性真是让人叹服,不得不佩服内核代码编写者的代码功底。

      最后,我们直接给出container_of的测试代码。

     1 /*************************************************************************
     2         > File Name: container_of.c
     3         > Author: yudongqun
     4         > Mail: qq2841015@163.com
     5         > Created Time: Thu 05 Nov 2020 05:16:36 PM CST
     6  ************************************************************************/
     7 
     8 #include <stdio.h>
     9 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
    10 #define container_of(ptr, type, member) ({
    11     const typeof( ((type *)0)->member ) *__mptr = (ptr);
    12     (type *)( (char *)__mptr - offsetof(type,member) );})
    13 
    14 #define NAME_LEN 20
    15 typedef struct {
    16     char name[NAME_LEN];
    17     int school_number;
    18 } Students;
    19 
    20 void func(int *p) {
    21     Students *stu = container_of(p, Students, school_number);
    22     printf("name: %s, school_number: %d
    ", stu->name, stu->school_number);
    23 }
    24 
    25 int main(void) {
    26     Students stu = {
    27         .name = "Tom",
    28         .school_number = 1234
    29     };
    30     func(&stu.school_number);
    31     return 0;
    32 }
    ydqun@VM-0-9-ubuntu 20201031 % gcc container_of.c                                                                 [0]
    ydqun@VM-0-9-ubuntu 20201031 % ./a.out                                                                            [0]
    name: Tom, school_number: 1234
    

      

  • 相关阅读:
    php页面调用微信扫一扫
    mysql大数据表添加字段
    Maven使用本地包的打包和安装(举例微信SDK)
    谈谈统计学正态分布阈值原理在数据分析工作中的运用
    深入剖析 RSA 密钥原理及实践
    Kafka 原理以及分区分配策略剖析
    jenkins+docker实现自动编译、打包、构建镜像、容器部署
    php7安装ldap扩展
    jenkins 简单实现php集成上线部署
    Jenkins 流水线(Pipeline)
  • 原文地址:https://www.cnblogs.com/ydqblogs/p/13930807.html
Copyright © 2020-2023  润新知