C语言数据结构之以“公”谋“私”

将公共数据结构放在较大的私有数据结构中,并使用指向公共数据结构的指针来检索指向私有数据结构的指针。

最近在看EDKII的源码,其中有个宏定义,
第一眼完全看不懂啥意思,套的一层又一层,拆分了解后感觉其对结构体的用法很巧妙。所以特意记录一下。

#define CR(Record, TYPE, Field, TestSignature)                                              \
    (DebugAssertEnabled () && (BASE_CR (Record, TYPE, Field)->Signature != TestSignature)) ?  \
    (TYPE *) (_ASSERT (CR has Bad Signature), Record) :                                       \
    BASE_CR (Record, TYPE, Field)
#define BASE_CR(Record, TYPE, Field)  ((TYPE *) ((CHAR8 *) (Record) - OFFSET_OF (TYPE, Field)))
#define OFFSET_OF(TYPE, Field)  ((UINTN) &(((TYPE *)0)->Field))

4个参数的含义:

参数介绍
Record指向Field的指针。
TYPE要返回的数据结构类型。Field字段必须在这个数据结构中。
FieldRecord所指向的字段的名称。
TestSignature要匹配的32位签名值。

拆分解释:

OFFSET_OF(TYPE, Field)  ((UINTN) &(((TYPE *)0)->Field))
//
计算 Field 成员在 TYPE 结构中的偏移量(以字节为单位)。
------------------------------------------------------------------------
(TYPE *)0	将整数 0 强制转换为指向 TYPE 类型的指针。
	        在C语言中,0 可以被视作任何类型的“空”指针,因此这个表达式创建了一个空指针,指向 TYPE 类型。
((TYPE *)0)->Field	 通过这个空指针访问 TYPE 结构体中的 Field 成员。
	                 由于这是一个空指针,实际上并不会访问任何真实的内存,但是编译器会根据 TYPE 结构体的定义来计算 Field 成员相对于结构体起始地址的偏移量。
&(((TYPE *)0)->Field)	取上面表达式结果的地址。
                        由于它是一个成员访问表达式,它的结果是一个右值(rvalue),代表了 Field 成员在结构体中的地址。取地址操作符 & 会得到这个地址值。 
(UINTN)	    将取地址操作的结果转换为 UINTN 类型,UINTN 通常是一个无符号整数类型,用于表示指针或大小等。
---------------------------------------------------------
这个技巧之所以有效,是因为编译器在编译时会根据结构体的布局来计算成员的偏移量,而不需要实际访问内存。
这种方法在编写宏定义或者内联函数时非常有用,因为它可以在编译时计算偏移量,而不需要运行时的内存访问。
//
#define BASE_CR(Record, TYPE, Field)  ((TYPE *) ((CHAR8 *) (Record) - OFFSET_OF (TYPE, Field)))
//
获取TYPE结构的起始地址,返回TYPE 类型的指针。
---------------------------------------------------------------------------------------------------
(CHAR8 *)(Record)	 将Record指针转换为CHAR8 *类型,即指向CHAR8(通常是unsigned char)的指针。
	                 这样做的目的是为了能够对Record指针进行算术操作,因为直接对不是CHAR8类型的指针进行减法操作在某些编译器中可能是不合法的。
(CHAR8 *)(Record) - RecordOffset	将Record指针的地址向前移动RecordOffset个字节,即移动到TYPE结构体的起始位置。
(TYPE *)	         将得到的CHAR8 *类型的指针再次转换为TYPE *类型,即指向TYPE结构体的指针。
----------------------------------------------------------------------------------------
这种技术常用于在不知道Record具体类型的情况下,通过结构体类型和成员名称来访问结构体中的成员的场景中。
//
#define CR(Record, TYPE, Field, TestSignature)                                              \
    (DebugAssertEnabled () && (BASE_CR (Record, TYPE, Field)->Signature != TestSignature)) ?  \
    (TYPE *) (_ASSERT (CR has Bad Signature), Record) :                                       \
    BASE_CR (Record, TYPE, Field)
//
如果启用了调试断言,且数据结构中签名不正确,则报错,否则返回此数据结构的指针。
----------------------------------------------------
DebugAssertEnabled()
----------------------------------------------------
这是一种轻量级的隐藏信息的方法,它将公共数据结构放在较大的私有数据结构中,
并使用指向公共数据结构的指针来检索指向私有数据结构的指针。
//

写了一小段代码尝试这个用法。

#include <stdio.h>
#include <uchar.h>

typedef struct _A {
	int aa;
	int bb;
	int cc;
}A;

void main()
{
	A ao = { 33, 66, 99 };
	printf("struct A size is %d\n", sizeof(A));

	int offset = (int)&(((A*)0)->cc);
	printf("cc offset is %d in A\n", offset);

	int val = ((A*)((char *)&(ao.cc) - offset))->bb;
	printf("bb is %d in A\n", val);
}

结果如下:

struct A size is 12
cc offset is 8 in A
bb is 66 in A
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值