将公共数据结构放在较大的私有数据结构中,并使用指向公共数据结构的指针来检索指向私有数据结构的指针。
最近在看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字段必须在这个数据结构中。 |
Field | Record所指向的字段的名称。 |
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