我遇到了以下c++代码:
#define OFFSETOF_MEMBER(t, f)
(reinterpret_cast<uintptr_t>(&reinterpret_cast<t*>(16)->f) - static_cast<uintptr_t>(16u)) // NOLINT
其中t是类型,f是字段名。我想知道为什么我们可以把一个整数16作为repret_cast的参数。
16是我们分配给指针的地址,它允许我们计算指定成员的偏移量。指针的地址只是一个数字,所以我们可以滥用这个事实来获取有关我们的结构/类的信息。
假设我们有一个结构:
struct point {
//Assuming 32-bit integer sizes.
//For 64-bit integersizes, 0x0, 0x8, 0x10 for the integer offsets
int x; //Offset 0x0
int y; //Offset 0x4
int z; //Offset 0x8
}; static_assert(sizeof(point) == 12 /* or 0xC in hex */);
我们使用宏:
OFFSETOF_MEMBER(point, y);
扩展宏,我们得到:
(reinterpret_cast<uintptr_t>(&reinterpret_cast<point*>(16)->y) - static_cast<uintptr_t>(16u)
reinterpret_cast<point*>(16)->y
的另一种表达方式可以是这样的:point * myPt = 16u;
我们知道16不是一个有效的地址,但编译器不是,只要我们不试图读取我们指向的地址,我们就可以了。
接下来,我们可以将所有的&reinterpret_cast<point*>(16)->y
简化为:&myPt->y
。从上面我们知道y是偏移量0x4,并且由于myPt是16:16 + 0x4 = 20
然后我们有reinterpret_cast<uintptr_t>(20u) - static_cast<uintptr_t(16u)
或20 - 16
,这给了我们y的偏移量,也就是0x4。
来自参考:
3(任何整数或枚举类型的值都可以转换为指针类型。[…]
因此,reinterpret_cast<>
在一定程度上就是为了做到这一点。
整数16只是一个内存地址。表达式reinterpret_cast<t*>(16)
的意思只是"将地址16处的对象解释为类型t
",但您知道该地址处没有这样的t
对象。理论上,16
可以被任何4x(32位(或8x(64位(整数所取代。如果选择0
,宏可以简化为:
#define OFFSETOF_MEMBER(t, f)
(reinterpret_cast<uintptr_t>(&reinterpret_cast<t*>(0)->f))
有关详细信息,请参见下页。