关于C语言结构转换的一些思考和总结
关于C语言结构转换的一些思考和总结。 ?成员变量c在struct node_t中的偏移量。
注:这里的偏移是指距结构起始位置的偏移。
看到这个问题,相信不同的人心里有不同的解决办法。下面我们分析一下以下可能的解决方案:
方法一
如果你熟悉c语言的话,如果你熟悉 的库函数,那么你首先想到的应该是offsetof函数(其实就是offsetof函数)它只是一个宏,我们暂时就这么称呼它)。我们用 man 3 offsetof 查看函数原型如下:
#include <stddef.h>
size_t offsetof(type, member);
一行代码就可以得到上面的库函数:
offsetof(struct node_t, c);
这当然不是本文的重点,拜托继续阅读。
方法二
如果我们对C语言的库函数不熟悉,这时候不用担心,我们仍然可以用自己的方法来解决问题。
最直接的想法就是:【结构体成员变量c的地址】减去【结构体起始地址】
我们先定义一个结构体变量节点:
struct node_t node;
然后计算成员。变量c的偏移量:
(unsigned long)(&(node.c)) - (unsigned long)(&node)
&(node.c)为结构体成员变量c的地址,强制转换为unsigned long;
&node为结构体起始地址,也必须转为unsigned long;
最后我们将上面的两个值相减,就得到了成员变量c的偏移量;
方法3
按照方法2的思路,我们还是可以不借助库函数的。获取成员变量c的偏移量。但作为程序员,我们需要能够独立思考。我们能否对上面的代码进行一些改进,让我们的代码更加简洁呢?在进行具体改进之前,我们应该分析一下方法2中的问题。
我想我不需要再说了。如果你细心的话,你可能已经注意到方法2的主要问题是我们修改了一个结构体变量节点。虽然标题并没有限制我们自定义变量,但是如果我们遇到严格的问题并且不允许在问题中自定义变量,我们就必须想出新的解决方案。
在探索新的解决方案之前,我们先讨论一个关于偏移的小问题:
小问题
这是一个简单的几何问题。假设A点在坐标轴上从A点移动到B点。点,你如何计算B相对于A的偏移量?这个问题对我们来说很简单,大多数人脱口而出并得到答案B-A。
所以这个答案完全正确?如果你再严格一些,你会觉得显然不是这样的。原因是,当A为坐标原点,即A=0时,上面的答案B-A就直接简化为B。
这个小而简单的问题对我们有什么启示呢?
我们将方法2的思想与上面的克莱因问题结合起来,我们很快就得到了如下的关联:
(unsigned long)(&(node.c)) - (unsigned long)(&node)
和
我们的想法一个小问题 如果A是坐标原点,则B-A简化为B,对应我们的方法2。如果节点的内存地址为0(&node==0),则上面的代码可以简化为:
(unsigned long)(&(node.c))
由于节点内存地址== 0,
node.c //结构体node中成员变量c
我们可以用另一种方式表达,如下:
((struct node_t *)0)->c
上面的代码应该更容易理解,因为我们知道该结构体的内存地址号为0,因此我们可以直接通过内存地址访问该结构体的成员变量。对应代码的含义是获取内存地址号为0的结构体struct node_t的成员变量c。
注:本文仅利用编译器的特性来计算结构体偏移量,并没有对结构体进行任何操作它的内存地址为0。可能有的同学对此还有疑问。有关此问题的详细信息,请参阅关于 C 语言对结构体成员变量访问方法的一些思考。
目前我们的offset方法去掉了自定义变量struct node_t node,直接用一行代码解决:
(unsigned long)(&(((struct node_t *)0)->c))
上面的代码是不是比方法2简单了?
这里我们将上面的代码函数定义为一个宏,用于计算结构体中成员变量的偏移量(后面的例子会用到这个宏):
#define OFFSET_OF(type, member) (unsigned long)(&(((type *)0)->member))
使用上面的宏,可以使用直接使用获取成员变量c在struct node_t中的偏移量为:
OFFSET_OF(struct node_t, c)
示例2
与示例1相同,我们首先定义如下要求:
已知结构体类型定义如下:
struct node_t{
char a;
int b;
int c;
};
int *p_c,该指针指向struct node_t x 的成员变量c
该结构体是1byte对齐的
#pragma pack(1)
请求:
结构体x的成员变量b的值是多少?
收到这个问题,我们先来做一个简单的分析。问题的意思是如何根据给定结构体的成员变量的引用找到该结构体的另一个成员变量的值。
可能的解决办法有:
方法一
由于我们知道结构体是1Byte对齐的,所以解决这个问题最简单的办法是:
*(int *)((unsigned long)p_c - sizeof(int))
上面的代码非常简单。将成员变量c的地址减去sizeof(int),得到成员变量b的地址,然后将其转换为int *,最后取值,最终得到成员变量b的值;
方法2
方法1虽然代码简单,但是扩展性不够好。我们希望指针p_node通过p_c直接指向结构体,然后通过p_node访问结构体的所有成员变量。
由此我们得到计算结构体起始地址p_node的思路:
[成员变量c的地址p_c]减去[c在结构体中的偏移量]
从示例1中我们得到树结构node_t中的成员变量c为:
(unsigned long)&(((struct node_t *)0)->c)
所以我们得到树的起始地址指针p_node为:
(struct node_t *)((unsigned long)p_c - (unsigned long)(&((struct node_t *)0)->c))
我们也可以直接使用示例1中定义的OFFSET_OF宏,那么上面的代码:
(struct node_t *)((unsigned long)p_c - OFFSET_OF(struct node_t, c))
最后我们可以使用下面的代码来获取成员变量a、b的值:
p_node->a
p_node->b
我们也将上面代码的功能定义如下宏:
#define STRUCT_ENTRY(ptr, type, member) (type *)((unsigned long)(ptr)-OFFSET_OF(type, member))
该宏的作用是通过结构体任意成员变量的指针来获取指向该结构体的指针。
我们利用上面的宏,将之前的代码修改如下:
STRUCT_ENTRY(p_c, struct node_t, c)
- p_c是对树结构node_t成员变量c的引用;
- struct node_t 结构类型;
- C 是指向 P_C 成员变量的指针 + 10 == P_A + SIzeof (Int)*10 = 0x95734104 + 4*10 = 0x95734144
(无符号) P_a + 10 == 0x95734104 + 10 = 0x957341 14
(char *)p_a + 10 == 0x9573 4104 + sizeof(char)*10 = 0x95734114
从以上三种情况,我想你应该能明白我想表达的意思了。 (注:未来的博客文章将从编译器的角度更深入地探讨这个主题)
结论
本文使用几个示例描述了有关 C 语言结构中偏移量的一些有趣的事情。我希望它能有所帮助。你帮忙了。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。