从成员指针到整个结构/类的强制转换

Casting from member pointer to whole struct/class

本文关键字:转换 结构 指针 成员      更新时间:2023-10-16

考虑以下代码:

#include <iostream>

struct bar {
double a = 1.0;
int b = 2;
float c = 3.0;
};
void callbackFunction(int* i) {
auto myStruct = reinterpret_cast<bar*>(i) - offsetof(bar, b);
std::cout << myStruct->a << std::endl;
std::cout << myStruct->b << std::endl;
std::cout << myStruct->c << std::endl;
//do stuff
}
int main() {
bar foo;
callbackFunction(&foo.b);
return 0;
}

我必须定义一个回调函数,并且我想在该函数中使用一些附加信息。我定义了自己的结构,并将成员的地址传递给函数。在函数中,我想通过强制转换"检索"整个结构,但指针似乎不匹配,我得到了错误的结果。我想我选角时做错了什么,但我不确定是什么?

您缺少一个使其工作的强制转换。在减去偏移量之前,您需要强制转换为字节类型,然后重新转换回bar*。原因是宏offsetof以字节数的形式返回偏移量。进行指针运算时,根据指针类型的大小进行减法和加法运算。让我们举一个例子:

假设您有一个bar实例,名为b,位于地址0x100h。假设sizeof(double) == 8sizeof(int) == 4sizeof(float) == 4,那么sizeof(bar) == 16和您的结构及其成员在内存中会是这样的:

b @ 0x100h
b.a @ 0x100h
b.b @ 0x108h
b.c @ 0x10Ch

则CCD_ 9将等于CCD_。您的原始代码说"将0x108h视为指向bar类型的结构。"。然后给我地址为0x108h - 8 * sizeof(bar)bar结构,或者具体地说:0x108h-0x80h=88h希望这个例子能说明为什么原始代码执行了错误的计算。

这就是为什么你需要告诉编译器,你想减去作为字节的地址,以获得结构中第一个成员的正确地址。

解决方案看起来像这样:

bar* owner = reinterpret_cast<bar*>(reinterpret_cast<char *>(i) - offsetof(bar, b));

有一件事你应该非常小心:只有当bar标准布局时,这才是合法的。您可以使用模板std::is_standard_layout<bar>::value进行静态断言,以验证您没有意外调用UB。

问题是,对于reinterpret_cast<bar*>(i),您基本上将i视为指向bar结构数组的第一个元素的指针。

这是有问题的,因为对于任何指针(或数组(p和索引i,表达式*(p + i)恰好等于p[i]

因此整个表达reinterpret_cast<bar*>(i) - offsetof(bar, b)&(reinterpret_cast<bar*>(i))[-offsetof(bar, b)]基本相似。也就是说,您得到一个指向这个"数组"中元素-offsetof(bar, b)的指针。这当然不是一个正确的索引。

如果你有一个字节的"数组",而不是bar结构的"阵列",它就会起作用:

char* tempPtr = reinterpret_cast<char*>(i) - offsetof(bar, b);
bar* myStructPtr = reinterpret_cast<bar*>(tempPtr);

您将指针向后移动了太多,b的偏移量是sizeof(double),所以可能是8,但表达式reinterpret_cast<bar*>(i) - offsetof(bar, b)将其移动了sizeof(bar) * sizeof(double)

虽然从技术上讲,将struct/class投射到其第一个成员是合法的,但您永远不应该需要这样做,这很容易导致UB

如果你只切换intdouble成员,那么int成员是第一个,那么,因为你的类是标准布局,你可以简单地将reinterpret_caststruct,并正常访问其他成员,因为第一个非静态数据成员和类对象将是指针可交换:

struct bar {  // Must be standard-layout!
int b = 2;  // Must be first non-static data member!
double a = 1.0;
float c = 3.0;
};
void callbackFunction(int* i) {
auto myStruct = reinterpret_cast<bar*>(i);
std::cout << myStruct->a << std::endl;
std::cout << myStruct->b << std::endl;
std::cout << myStruct->c << std::endl;
//do stuff
}

int main() {
bar foo;
callbackFunction(&foo.b);
return 0;
}