通过宏定义求结构体变量的偏移量

前言

  在平常的编程中,经常需要用到一种数据结构:结构体,当结构体中的成员足够的多,且成员的数据类型又足够统一的情况下,外部函数需要操作其中的一个成员,有两种较为直接的方法:
  1、取整个结构体的数据,对需要的数据进行更新,再更新整个结构体
  2、针对该参数专门编写独立的接口供外部使用
  
  这两种方法在日常使用过程中接触的比较多,方便快捷,但当遇到以下情况时,就变得不那么通用了。
  
  假设结构体中有100个char型的data,从data1到data100,如果我们一次只需操作其中的一个同类型参数,而对整个结构体数据进行更新,第一种方法无疑做了很多没必要的工作。
  
  如果操作具体的参数不是本函数内部决定,而是受外部的变化影响,data1-100都有可能涉及,但直至调用时,才知道具体参数是什么,针对这100个数据专门编写特定的接口一一对应,无疑是繁琐的。
  
  此时偏移量的优势就出现了,你无需知道参数名称是什么,具体参数是哪个,只需要知道偏移量是多少,然后负责将结构体中指定偏移量的数据进行更改即可,而计算的工作完全可以交给调用函数去实现,使得模块间的耦合性降低,模块的功能更单一,更专一。

typedef unsigned int uint32;
#define OFFSETOF(type,field)	((uint32)&((type*)0)->field)
语句注释
(type*)0把地址为0的指针指向type类型的结构体
((type*)0)->field取结构体变量
&((type*)0)->field取结构体变量地址
((uint32)&((type*)0)->field)将地址转换成uin32型

测试代码

  接下来的这段代码中,我们定义两个结构体UTEST_T及UTEST1_T,这两个结构体的成员及成员对应的数据类型都是一致的,唯一不同的仅仅是结构体成员在结构体中的位置,通过计算及打印成员在结构体中的偏移量,观察结构体在内存中的存放规律,来更好的理解偏移量在实际使用中的直观感受。

#include<stdio.h>

typedef unsigned int uint32;
#define OFFSETOF(type,field)	((uint32)&((type*)0)->field)

#define CONTAINER_OF(ptr, type, member) \
					((type *)((char *)(ptr) - OFFSETOF(type,member)))

typedef struct UTEST_
{
	char 	a;
	char 	b;
	short 	c;
	int 	d;

}UTEST_T;

typedef struct UTEST1_
{
	char 	a;
	int 	b;
	char 	c;
	short 	d;

}UTEST1_T;

int main()
{
	UTEST_T test = {0};
		
	printf("UTEST --- a:%d b:%d c:%d d:%d size:%d \n", OFFSETOF(UTEST_T,a), OFFSETOF(UTEST_T, b),OFFSETOF(UTEST_T, c), 
					OFFSETOF(UTEST_T, d),sizeof(UTEST_T));

	printf("UTEST1--- a:%d b:%d c:%d d:%d size:%d\n", OFFSETOF(UTEST1_T,a), OFFSETOF(UTEST1_T, b),OFFSETOF(UTEST1_T, c), 
					OFFSETOF(UTEST1_T, d),sizeof(UTEST1_T));
					
	printf("struct add:%d \r\nstruct.a add:%d\r\n",&test,&test.a);

	return 0;
}

输出结果UTEST  — a:0 b:1 c:2 d:4   size:8
     UTEST1 — a:0 b:4 c:8 d:10 size:12
     struct add:1638208
     struct.a add:1638208

对于UTEST_T(以下单位均为字节):
   1、a的偏移量为0,因为a的地址和结构体的首地址是一样的;
   2、b的偏移量为1,因为a的大小为1;
   3、c的偏移量为2,因为a的大小为1,b的大小为1,1+1=2;
   4、d的偏移量为4,因为a的大小为1,b的大小为1,c的大小为2;
  
  对于UTEST1_T(以下单位均为字节):
   1、a的偏移量为0,a的地址减去结构体首地址 == 偏移量;
   2、b的偏移量为4,字节对齐,a的数据占1个字节,字节对齐补充了3个字节; 
   3、c的偏移量为8,因为a和b各占了4个字节,4+4=8; 
   4、d的偏移量为10,c和d共用4个字节,c使用第9/10个字节,d使用第11/12个字节
  
   可见,在平时编程中要保持良好的代码风格和习惯,定义数据时尽量4字节对齐的去考虑,而不是想到什么定义什么,不够4字节的可以考虑定义保留位去凑满,也方面后续功能添加时代码变更的灵活性。

扩展

  在平时使用结构体当中,会遇到一种情况,如果结构体的内容比较简洁单一,直接操作具体成员方便快捷,但当结构体中嵌套其余结构体,比如嵌套了一个双向链表用于排序,此时我们只知道链表指针地址的情况下,如何获取整个结构体的信息,这时候可以运用上面的知识,通过计算偏移量获取结构体的首地址,但需要做一点小小的改变,原型是Linux内核编程中的一个宏函数CONTAINER_OF(ptr, type, member),上面的代码中也贴了出来。

#if 1
#define CONTAINER_OF(ptr, type, member) \
					((type *)((char *)(ptr) - OFFSETOF(type,member)))
#endif
		
#if 0
#define CONTAINER_OF(ptr, type, member) ({ 	\
	const __typeof(((type *)0)->member) *__mptr = (ptr);    \
	(type *)( (char *)__mptr - offsetof(type,member));})
#endif

贴张图方便理解
1111

  可以看到该宏仅仅多了一个指针ptr,而其余的变化并不大,我们先分步理解,其实它并没有那么难消化,细嚼慢咽慢慢来,OFFSETOF已经知道了嘛,获取某个参数的偏移量,重点是前面多了(char *)(ptr),ptr就是一个普通的指针,由于地址计算是以字节为单位,所以强制转换成char *型的指针,比如该指针指向b,地址为1638208,test_t中b的偏移量为4,那么1638208 - 4 = 1638204,即为 test_t的首地址,就这么简单。

至于这一句:const __typeof(((type *)0)->member) *__mptr = (ptr); 这个就精妙了朋友们,typeof是gun c的编译环境下才能使用的,作用是获取某个变量的数据类型,这里定义一个指向类型与ptr指向的数据类型相同,地址为0的指针,简单的说就是拐弯抹角定义一个和ptr一样的指针,只是该指针地址为0,然后用该指针指向ptr,如果此时数据类型出错的话,那么编译器就会产生警告,这一句对整体的宏函数没有什么影响,注释掉之后可以像下面测试代码中去使用,只是当你传参错误后,有这一句编译器会警告,防止你在代码中埋雷。

测试代码

#include<stdio.h>

#define OFFSETOF(type, member) 	((unsigned int) &((type *)0)->member)

#define CONTAINER_OF(ptr, type, member) \
					((type *)((char *)(ptr) - OFFSETOF(type,member)))

typedef struct test_
{
	int a;
	int b;
}test_t;

typedef struct test1_
{
	test_t data1;
	int data2;
}test1_t;

int main()
{
	int *p = NULL;
	test1_t test1 = {0};
	test1_t *ptTest1 = NULL;

	test1.data1.b = 1;
	test1.data2 = 2;

	p = &test1.data1.b;

	ptTest1 = CONTAINER_OF(p,test_t,b);

	printf("ptTest1->data2 : %d\r\n",ptTest1->data2);

	return 0;
}

输出结果ptTest1->data2 : 2

参考:写宏定义:得到一个field在结构体(struct type)中的偏移量

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值