【C】取出结构中成员变量偏移位置的宏定义

    昨天看到的一句代码,是用来取出结构中成员变量相对偏移位置的宏,如下:

    #define OFFSET_OF_STRUCT(type, var) ((size_t)(&((type *)NULL)->var))

    举个例子



    #include <stdio.h>
    #define OFFSET_OF_STRUCT(type, var) ((size_t)(&((type *)NULL)->var))
    
    typedef struct person
    {
            int     age;
            char    name[10];
            long    num1;
            float   num2;
            double  num3;
            int     num4;
    } PERSON;
    
    int main()
    {
            size_t offset0 = OFFSET_OF_STRUCT(PERSON, age);
            size_t offset1 = OFFSET_OF_STRUCT(PERSON, name);
            size_t offset2 = OFFSET_OF_STRUCT(PERSON, num1);
            size_t offset3 = OFFSET_OF_STRUCT(PERSON, num2);
            size_t offset4 = OFFSET_OF_STRUCT(PERSON, num3);
            size_t offset5 = OFFSET_OF_STRUCT(PERSON, num4);
            printf("%d %d %d %d %d %d\n",
                    offset0, offset1, offset2, offset3, offset4, offset5);
            return 0;
    }

    执行结果:0 4 16 24 32 40

    代码分析

    比较费解的一句宏定义语句,抽出一句语句来分析看看:

    size_t offset2 = OFFSET_OF_STRUCT(PERSON, num1);

    将宏定义手动代入展开如下:

    size_t offset2 = ((size_t)(&((PERSON *)NULL)->num1)) ;

    NULL即为0,(PERSON *)NULL即将0地址转为PERSON*的指针,&((...)->num1)取出指针所指向结构对应的num1成员的地址;num1成员的地址等于指针的地址加上num1在结构中的相对偏移,此处指针地址为0,于是num1成员的地址正好等于num1在结构中的相对偏移,于是将num1成员的地址强制转化为int型,结果正好是num1在结构中的相对偏移位置。解释完毕。

    不得不说这是一个很巧妙的trick。

    补遗

    将上面宏定义中的size_t改为int,NULL改为值为NULL的指针,结果编译的时候出现了warning,代码如下:

    #include <stdio.h>
    
    typedef struct person
    {
            int     age;
            char    name[10];
            long    num1;
            float   num2;
            double  num3;
            int     num4;
    } PERSON;
    
    int main()
    {
            int off1 = 0;
            int off2 = 0;
            struct person *nul= NULL;
    
            printf("%d %d ",
                    sizeof(&(((struct person *)nul)->name)),
                    sizeof(&(((struct person *)0)->name)));
            off1 = (int)(&(((struct person *)nul)->name));
            off2 = (int)(&(((struct person *)0)->name));
            printf("%d %d\n", off1, off2);
            return 0;
    }

    编译的提示信息如下:

    t.c: In function ‘main’:
    t.c:23: warning: cast from pointer to integer of different size

    将代码做了简化,如下:

    #include <stdio.h>
    
    int main()
    {
            int off1 = 0;
            int off2 = 0;
            int *nul= 0;
            printf("%d %d ",
                    sizeof((int *)0),
                    sizeof((int *)nul));
            off1 = (int)((int *)0);
            off2 = (int)((int *)nul);
            printf("%d %d\n", off1, off2);
            return 0;
    }

    查了一下,出现此问题的原因是因为64位的系统中地址是8字节的(32位系统下是4字节的),而int长度是4字节的,于是系统编译时给出了一个警告信息。

    但观察使用0地址的情况,同样是8字节的地址却没有给出任何警告,确实奇怪。这里只能猜测是常量存放的区域和处理方式的不同导致的,一下子也不知道该从哪个角度去分析这个问题,先记录一笔。有知道的同学欢迎留言解答,拜谢!

    2011-3-30 更新

    将宏定义的int改为size_t,在32位和64位架构下可以兼容;参见【C】int与size_t的区别



    本博客所有文章如无特别注明均为原创。
    复制或转载请以超链接形式注明转自枫芸志,原文地址《【C】取出结构中成员变量偏移位置的宏定义
    标签:
    分享:

还没有人抢沙发呢~

无觅相关文章插件,快速提升流量