从成员指针转换为整个 struct/class
Casting from member pointer to whole struct/class
考虑以下代码:
#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;
}
我必须定义一个回调函数,并且我想在该函数中使用一些附加信息。我定义了自己的结构并将成员的地址传递给函数。在函数中,我想通过转换 "retrieve" 整个结构,但指针似乎不匹配,我得到了错误的结果。我想我在选角时做错了什么,但我不确定是什么?
你缺少演员来完成这项工作。您需要在减去偏移量之前转换为字节类型,然后再转换回 bar*
。原因是宏 offsetof
returns 将偏移量作为字节数。当您进行指针运算时,减法和加法根据指针类型的大小进行计算。举个例子:
假设您有一个名为 b
的 bar
实例,地址为 0x100h。假设 sizeof(double) == 8
、sizeof(int) == 4
和 sizeof(float) == 4
,那么 sizeof(bar) == 16
和您的结构及其成员在内存中看起来像这样:
b @ 0x100h
b.a @ 0x100h
b.b @ 0x108h
b.c @ 0x10Ch
offsetof(bar,b)
等于 8
。您的原始代码说“将 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。
您将指针向后移动太多,b
的偏移量为 sizeof(double)
,因此可能为 8,但表达式 reinterpret_cast<bar*>(i) - offsetof(bar, b)
将其移动了 sizeof(bar) * sizeof(double)
.
虽然将 struct/class
转换为第一个成员在技术上是合法的,但您永远 不需要 这样做,它很容易导致 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)]
基本相似。也就是说,你在这个 "array" 中得到一个指向元素 -offsetof(bar, b)
的指针。这当然不是正确的索引。
如果你有一个字节的"array"而不是bar
结构的"array",它会起作用:
char* tempPtr = reinterpret_cast<char*>(i) - offsetof(bar, b);
bar* myStructPtr = reinterpret_cast<bar*>(tempPtr);
如果您只是切换 int
和 double
成员,让 int
成员排在第一位,那么,因为您的 class 是标准布局,您可以简单地 reinterpret_cast
到 struct
并正常访问其他成员,因为第一个非静态数据成员和 class 对象将是 pointer-interconvertible:
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;
}
考虑以下代码:
#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;
}
我必须定义一个回调函数,并且我想在该函数中使用一些附加信息。我定义了自己的结构并将成员的地址传递给函数。在函数中,我想通过转换 "retrieve" 整个结构,但指针似乎不匹配,我得到了错误的结果。我想我在选角时做错了什么,但我不确定是什么?
你缺少演员来完成这项工作。您需要在减去偏移量之前转换为字节类型,然后再转换回 bar*
。原因是宏 offsetof
returns 将偏移量作为字节数。当您进行指针运算时,减法和加法根据指针类型的大小进行计算。举个例子:
假设您有一个名为 b
的 bar
实例,地址为 0x100h。假设 sizeof(double) == 8
、sizeof(int) == 4
和 sizeof(float) == 4
,那么 sizeof(bar) == 16
和您的结构及其成员在内存中看起来像这样:
b @ 0x100h
b.a @ 0x100h
b.b @ 0x108h
b.c @ 0x10Ch
offsetof(bar,b)
等于 8
。您的原始代码说“将 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。
您将指针向后移动太多,b
的偏移量为 sizeof(double)
,因此可能为 8,但表达式 reinterpret_cast<bar*>(i) - offsetof(bar, b)
将其移动了 sizeof(bar) * sizeof(double)
.
虽然将 struct/class
转换为第一个成员在技术上是合法的,但您永远 不需要 这样做,它很容易导致 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)]
基本相似。也就是说,你在这个 "array" 中得到一个指向元素 -offsetof(bar, b)
的指针。这当然不是正确的索引。
如果你有一个字节的"array"而不是bar
结构的"array",它会起作用:
char* tempPtr = reinterpret_cast<char*>(i) - offsetof(bar, b);
bar* myStructPtr = reinterpret_cast<bar*>(tempPtr);
如果您只是切换 int
和 double
成员,让 int
成员排在第一位,那么,因为您的 class 是标准布局,您可以简单地 reinterpret_cast
到 struct
并正常访问其他成员,因为第一个非静态数据成员和 class 对象将是 pointer-interconvertible:
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;
}