前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C语言 | offsetof宏和container_of宏

C语言 | offsetof宏和container_of宏

作者头像
飞哥
发布2020-07-10 10:27:36
2.1K0
发布2020-07-10 10:27:36
举报

今天分享C语言中的两个宏,这两个宏包含了指针和结构体的知识,非常具有代表性。另外,这个题目曾经是大疆无人机的一道笔试题,可见,这两个宏对C语言基础还是有一定要求的。先说明一下,今天所有的例子都是以32位系统来说的。

废话不多说,今天要说的两个宏分别是offsetof和container_of,第一个宏是用来计算结构体中某个成员相对于结构体的偏移量,第二个宏是已知指向结构体某个成员的指针,来计算结构体的指针。来看一下它们的原型:

代码语言:javascript
复制
define offsetof(TYPE, MEMBER) ((int) &((TYPE *)0)->MEMBER)
代码语言:javascript
复制
#define container_of(ptr, type, member) ({            \
    const typeof(((type *)0)->member) * __mptr = (ptr); \
    (type *)((char *)__mptr - offsetof(type, member)); })

下面分别来介绍一下这两个宏。

一、offsetof

这个宏是用来计算结构体某个成员的偏移量的,所以我们先来定义一个简单的结构体类型,来说明。

代码语言:javascript
复制
struct mystruct
{
    char a;         // 0
    int b;          // 4
    short c;        // 8
};

利用这个机构体类型定义一个结构体变量

代码语言:javascript
复制
struct mystruct  s;

在这个结构体当中,有三个成员变量,本来char类型是占一个字节,int类型是占4个字节,short类型是占2个字节,一共占7个字节,但是根据结构体的三个对齐原则,我们知道在这里,char占了4个字节,int占了4个字节,short占了4个字节(关于结构体对齐原则不是今天介绍的重点,所以不多介绍)。所以c实际上的偏移量是8,而不是5。这里因为结构体的成员很少,且类型不复杂,所以可以自己手动算出来,但是如果结构体更复杂一些,我们就不可能自己手动去算了,那有什么好的办法呢?

我们知道,C语言给我们提供了一个很好的方式去访问结构体成员,比如结构体变量我们可以用点.去访问,结构体指针我们可以用->去访问,这两种访问方式本质上是通过指针进行访问的,只不过这个过程是编译器帮我们处理了。

比如我们要给变量c赋值,我们可以用简单的方法:

s.c=12;

我们也可以用指针的方法:

short *p=(short*)((int)&s+8);

*p=12;

显然第二种方法要麻烦的多,并且要自己计算偏移量,还要知道变量类型,所以C语言帮我们考虑了这一点,使用简单的点的方式就行了。

既然C语言帮我们做了计算偏移量这件事情,那我们是不是可以反过来利用一下它,先通过点的方式访问变量,再对变量进行取地址运算,减去结构体首地址不就是变量的偏移量了吗?如果首地址是0的话就更好了,直接取地址之后就是偏移量了。

没错,这就是这个宏的思路。

(TYPE *)0:就是将地址0转化为TYPE类型的指针;

((TYPE *)0)->MEMBER通过结构体指针来访问结构体变量;

&((TYPE *)0)->MEMBER对结构体变量进行取地址运算;

((int) &((TYPE*)0)->MEMBER)最后将地址转化为整形,这一步其实可以省略,看你是需要返回整形还是直接返回地址。

我们可以做个简单的实验来验证这一点

二、container_of

上面介绍了offsetof宏的使用,相信不是那么难理解,那么这个宏就看起来复杂多了,但是,其实只要把思路理清楚了,也不是那么复杂。这个宏我在VC6.0编译器上编译的时候是会报错的,其中的typeof这个关键字它就不认识,因此没法做实验,但是在gcc编译器上是可以的,估计因为这个原因,使用的会更少一些,但是这没关系,重要的在于我们能够理解它的原理。

下面是我用这个宏在gcc上做的实验:

这个宏的作用是已知某个结构体成员变量的指针,反过来得到结构体的地址。其实有了上面的那个基础,这个会更简单一些。

既然有了指向结构体成员变量的指针,那么也就是说知道了这个变量的地址,如果我们又知道了这个变量的偏移量,那么利用这个变量的地址减去它的偏移量不就知道结构体地址了吗?没错,它的思路就是这么简单。

下面将这个宏拆分来理解:

((type *)0):将地址0转化为type类型的指针;

((type *)0)->member:通过指针访问member这个成员;

typeof(((type*)0)->member):获取member这个成员的数据类型;

typeof(((type*)0)->member) * __mptr:利用获取的这个类型来定义一个指针变量;

typeof(((type*)0)->member) * __mptr = (ptr);利用上面定义的指针来指向你已知的那个结构成员。

((char *)__mptr -offsetof(type, member));将指针转化为char类型,并且减去偏移量。这里要注意的就是这个偏移量是int类型的,上面说到计算偏移量时可以不强制转化为int型,但是这里做加减时就必须转化为int型了,因为char*类型不能和指针相加减,只能和数字相加减。

(type *)((char *)__mptr -offsetof(type, member));最后将获取的指针再转化为type类型。

可能前面几句可以理解,后面就糊涂了。其实也不难理解,我举个简单的例子。

int *p=&b; //p指向成员b;

((char *)p-4) //p减去偏移量4,不就是结构体地址了吗,只不过这个是char*类型的指针,如果要将它还原成结构体,还得再强制类型转化一次。

(struct mystruct*) ((char *)p-4);

可能还是有些人不理解为什么要先转化成char*类型之后再减4,那么这就涉及到指针的加减问题了。

我们知道,在内存当中,是按字节为单位来编址的,可以想象为一个字节就是一个个的小格子,每个小格子都有一个编号,这个编号实际上就是地址。如果我们定义一个 int *p;那么,每次p加一或者减一都是移动四个字节,而如果定义一个 char *p,那么每次p加一或者减一都是移动一个字节,换句话说,p一次移动的字节数就是它指向的变量的类型所占的字节数。上面因为返回的偏移量是以字节为单位的,所以必须先转化为char*类型才能加减,加减完之后再转化为你需要的类型。

实际上,也不是一定要转化成char*类型的,我们可以将地址先转化为int类型的,加减完之后再转化为指针,这两种方式都是一样的。我们可以做一个小实验来证明这一点

我们可以发现,这两种方法都可以准确的还原结构体的地址。

代码清单:

代码语言:javascript
复制
#include <stdio.h>

struct mystruct
{
    char a;         // 0
    int b;          // 4
    short c;        // 8
    int d;          //c
};


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


int main(void)
{
    struct mystruct  s;
    printf("s的地址为:%p\n",&s);
    int *p;
    p=&(s.d);
    printf("p的地址为:%p\n",p);
    printf("p的偏移量%d\n",offsetof(struct mystruct,d));   
    printf("方式一得到的s的地址为:%p\n", (struct mystruct *)  ( (char*)p-offsetof(struct mystruct,d)));
    printf("方式二得到的s的地址为:%p\n", (struct mystruct *)  ( (int)p-offsetof(struct mystruct,d)));

    return 0;
}

以上就是今天要分享的内容,实际上,这些内容所涉及到的东西是很多的,比如指针,结构体,强制类型转化等等。但是这些归根结底来说,是对内存和数据类型要有非常深刻的理解。必须要先搞清楚什么是内存,还有数据类型的含义到底是什么,变量是什么,变量和数据类型的关系,才能理解上面说的东西,否则的话只是表面懂了,稍微变化一下就不知道怎么办了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 电子技术研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档