【问题标题】:Casting from member pointer to whole struct/class从成员指针转换为整个结构/类
【发布时间】:2020-02-21 18:20:20
【问题描述】:

考虑以下代码:

#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;
}

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

【问题讨论】:

    标签: c++ function struct casting callback


    【解决方案1】:

    您缺少一个演员来完成这项工作。您需要在减去偏移量之前转换为字节类型,然后重新转换回bar*。原因是宏offsetof 将偏移量作为字节数返回。当您进行指针算术时,减法和加法根据指向类型的大小工作。举个例子吧:

    假设您有一个名为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&lt;bar*&gt;(reinterpret_cast&lt;char *&gt;(i) - offsetof(bar, b));

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

    【讨论】:

    • 感谢您的详尽解释。
    • 我想我仍然需要阅读指针算法 :D 但现在我明白了这个问题。
    • 直观的思考方式(对我而言)是指针算法在常见情况下是正确的:如果你有一个int* a,那么a + 1 会给你下一个 int记忆,在第一个“结束”之后,可以这么说。如果你有一个double* a,那么a + 1 会给你下一个内存中的双倍。如果你有一个HugeStruct1KB* hugeStructhugeStruct +1 会给你下一个这样的结构——它的偏移量与简单整数的偏移量在直觉上是不同的。
    【解决方案2】:

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

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

    所以整个表达式reinterpret_cast&lt;bar*&gt;(i) - offsetof(bar, b)&amp;(reinterpret_cast&lt;bar*&gt;(i))[-offsetof(bar, b)]基本相似。也就是说,您会在此“数组”中获得指向元素 -offsetof(bar, b) 的指针。这当然不是一个正确的索引。

    如果你有一个 bytes 的“数组”而不是 bar 结构的“数组”,它会起作用:

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        如果你只是切换 intdouble 成员,使 int 成员是第一个,那么,因为你的类是标准布局,你可以简单地将 reinterpret_caststruct 和正常访问其他成员,因为第一个非静态数据成员和类对象将是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;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-04-29
          • 2015-01-24
          • 1970-01-01
          • 2015-03-11
          • 2021-08-05
          • 2015-02-15
          • 1970-01-01
          相关资源
          最近更新 更多