从成员指针转换为整个 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 将偏移量作为字节数。当您进行指针运算时,减法和加法根据指针类型的大小进行计算。举个例子:

假设您有一个名为 bbar 实例,地址为 0x100h。假设 sizeof(double) == 8sizeof(int) == 4sizeof(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);

如果您只是切换 intdouble 成员,让 int 成员排在第一位,那么,因为您的 class 是标准布局,您可以简单地 reinterpret_caststruct 并正常访问其他成员,因为第一个非静态数据成员和 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;
}