关于c语言中结构体偏移的一些思考和总结。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
示例1
我们先来定义一下需求:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
已知结构体类型定义如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
struct node_t{
char a;
int b;
int c;
};
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
且结构体1Byte对齐文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
#pragma pack(1)
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
求:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
结构体struct node_t中成员变量c的偏移。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
注:这里的偏移量指的是相对于结构体起始位置的偏移量。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
看到这个问题的时候,我相信不同的人脑中浮现的解决方法可能会有所差异,下面我们分析以下几种可能的解法:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
方法1
如果你对c语言的库函数比较熟悉的话,那么你第一个想到的肯定是offsetof函数(其实只是个宏而已,先姑且这样叫着吧),我们man 3 offsetof查看函数原型如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
#include <stddef.h>
size_t offsetof(type, member);
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
有了上述的库函数,我们用一行代码就可以搞定:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
offsetof(struct node_t, c);
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
当然这并非本文探讨的重点,请继续阅读。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
方法2
当我们对c语言的库函数不熟悉的时候,此时也不要着急,我们依然可以使用我们自己的方法来解决问题。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
最直接的思路是:【结构体成员变量c的地址】 减去 【结构体起始地址】文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
我们先来定义一个结构体变量node:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
struct node_t node;
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
接着来计算成员变量c的偏移量:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
(unsigned long)(&(node.c)) - (unsigned long)(&node)
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
&(node.c)为结构体成员变量c的地址,并强制转化为unsigned long;文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
&node为结构体的起始地址,也强制转化为unsigned long;文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
最后我们将上述两值相减,得到成员变量c的偏移量;文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
方法3
按照方法2的思路我们在不借助库函数的情况下,依然可以得到成员变量c的偏移量。但作为程序员,我们应该善于思考,是不是可以针对上面的代码做一些改进,使我们的代码变得更简洁一些?在做具体的改进之前,我们应该分析方法2存在哪些方面的问题。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
相信不用我多说,细心的你一定已经察觉到,方法2中最主要的一个问题是我们自定义了一个结构体变量node,虽然题目中并未限制我们可以自定义变量,但当我们遇到比较严且题目中不允许自定义变量的时候,此时我们就要思考新的解决方法。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
在探讨新的解决方法之前,我们先来探讨一个有关偏移的小问题:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
小问题文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
这是一道简单的几何问题,假设在座标轴上由A点移动到B点,如何计算B相对于A的偏移?这个问题对于我们来说是非常的简单,可能大部分人都会脱口而出并得到答案为B-A。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
那么这个答案是否完全准确呢?比较严谨的你觉得显然不是,原因在于,当A为坐标原点即A=0的时候,上述答案B-A就直接简化为B了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
这个小小的简单的问题,对于我们来说有什么启示呢?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
我们结合方法2的思路和上述的小问题,是不是很快就得到了下面的关联:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
(unsigned long)(&(node.c)) - (unsigned long)(&node)
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
和文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
B - A
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
我们小问题的思路是当A为坐标原点的时候,B-A就简化为B了,那么对应到我们的方法2,当node的内存地址为0即(&node==0)的时候,上面的代码可简化为:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
(unsigned long)(&(node.c))
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
由于node内存地址==0了,所以文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
node.c //结构体node中成员变量c
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
我们就可以使用另外一种方式来表达了,如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
((struct node_t *)0)->c
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
上述代码应该比较好理解,由于我们知道结构体的内存地址编号为0,所以我们就可以直接通过内存地址的方式来访问该结构体的成员变量,相应的代码的含义就是 获取内存地址编号为0的结构体struct node_t的成员变量c。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
注:此处只是利用了编译器的特性来计算结构体偏移,并未对内存地址0有任何操作,有些同学对此可能还有些疑问,详细的了解该问题可参考关于c语言结构体成员变量访问方式的一点思考。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
此时,我们的偏移求法就消除了struct node_t node这个自定义变量,直接一行代码解决,:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
(unsigned long)(&(((struct node_t *)0)->c))
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
上述的代码相对于方法2是不是更简洁了一些。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
这里我们将上面的代码功能定义为一个宏,该宏的作用是用来计算某结构体内成员变量的偏移(后面的示例会使用该宏):文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
#define OFFSET_OF(type, member) (unsigned long)(&(((type *)0)->member))
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
使用上面的宏,就可以直接得到成员变量c在结构体struct node_t中的偏移为:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
OFFSET_OF(struct node_t, c)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
示例2
和示例1一样,我们先定义需求如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
已知结构体类型定义如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
struct node_t{
char a;
int b;
int c;
};
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
int *p_c,该指针指向struct node_t x的成员变量c文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
结构体1Byte对齐文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
#pragma pack(1)
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
求:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
结构体x的成员变量b的值?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
拿到这个问题的时候,我们先做一下简单的分析,题目的意思是根据一个指向某结构体成员变量的指针,如何求该结构体的另外一个成员变量的值。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
那么可能的几种解法有:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
方法1
由于我们知道结构体是1Byte对齐的,所以这道题最简单的解法是:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
*(int *)((unsigned long)p_c - sizeof(int))
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
上述代码很简单,成员变量c的地址减去sizeof(int)从而得到成员变量b的地址,然后再强制转换为int *,最后再取值最终得到成员变量b的值;文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
方法2
方法1的代码虽然简单,但扩展性不够好。我们希望通过p_c直接得到指向该结构体的指针p_node,然后通过p_node访问该结构体的任意成员变量了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
由此我们得到计算结构体起始地址p_node的思路为:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
【成员变量c的地址p_c】减去【c在结构体中的偏移】文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
由示例1,我们得到结构体struct node_t中成员变量c的偏移为:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
(unsigned long)&(((struct node_t *)0)->c)
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
所以我们得到结构体的起始地址指针p_node为:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
(struct node_t *)((unsigned long)p_c - (unsigned long)(&((struct node_t *)0)->c))
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
我们也可以直接使用示例1中定义的OFFSET_OF宏,则上面的代码变为:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
(struct node_t *)((unsigned long)p_c - OFFSET_OF(struct node_t, c))
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
最后我们就可以使用下面的代码来获取成员变量a,b的值:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
p_node->a
p_node->b
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
我们同样将上述代码的功能定义为如下宏:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
#define STRUCT_ENTRY(ptr, type, member) (type *)((unsigned long)(ptr)-OFFSET_OF(type, member))
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
该宏的功能是通过结构体任意成员变量的指针来获得指向该结构体的指针。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
我们使用上面的宏来修改之前的代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
STRUCT_ENTRY(p_c, struct node_t, c)
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
- p_c为指向结构体struct node_t成员变量c的指针;
- struct node_t结构体类型;
- c为p_c指向的成员变量;
注:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
上述示例中关于地址运算的一些说明:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
int a = 10;
int * p_a = &a;
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
设p_a == 0x95734104;文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
以下为编译器计算的相关结果:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
p_a + 10 == p_a + sizeof(int)*10 =0x95734104 + 4*10 = 0x95734144文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
(unsigned long)p_a + 10 == 0x95734104+10 = 0x95734114文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
(char *)p_a + 10 == 0x95734104 + sizeof(char)*10 = 0x95734114文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
从上述三种情况,相信你应该能体会到我所要表达的意思了。(注:后续某博文将从编译器的角度对该问题进行详细的阐述)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html
结论
本文通过几个示例描述了c语言结构体有关偏移的一些有意思的事情,希望能够对你有所帮助。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/bc/4631.html