Casting from member pointer to whole struct/class

a 夏天 提交于 2021-02-19 03:50:26

问题


Consider following code:

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

I have to define a callback function and I want to use some additional information in that function. I defined my own struct and pass the address of a member to the function. In the function I want to "retrieve" the whole struct by casting, but the pointers don't seem to match and I get wrong results. I guess I'm doing something wrong while casting but I'm not sure what?


回答1:


You're missing a cast to make this work. You need to cast to a byte type before subtracting the offset, and then recast back to bar*. The reason is that the macro offsetof returns the offset as a number of bytes. When you do pointer arithmetic, subtraction and addition work in terms of the size of the pointed type. Let's make an example:

Let's assume that you have a bar instance, named b that is at address 0x100h. Assuming that sizeof(double) == 8, sizeof(int) == 4 and sizeof(float) == 4, then sizeof(bar) == 16 and your struct and its members would look like that in memory:

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

offsetof(bar,b) would be equal to 8. Your original code says 'treat 0x108h as if it's pointing to a struct of type bar. then give me the bar struct at address 0x108h - 8 * sizeof(bar), or specifically: 0x108h - 0x80h = 88h.' Hopefully the example demonstrates why the original code was performing the wrong calculation.

This is why you need to tell the compiler that you want to subtract the addresses as bytes instead, to get to the correct address of the first member in your struct.

The solution would look something like this:

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

One thing that you should be very careful about: This is legit only if bar is standard layout. You can use the template std::is_standard_layout<bar>::value to make a static assert to verify you're not accidentally invoking UB.




回答2:


The problem is that with reinterpret_cast<bar*>(i) you basically treat i as a pointer to the first element of an array of bar structures.

This is problematic because for any pointer (or array) p and index i, the expression *(p + i) is exactly equal to p[i].

So the whole expression reinterpret_cast<bar*>(i) - offsetof(bar, b) is basically similar to &(reinterpret_cast<bar*>(i))[-offsetof(bar, b)]. That is, you get a pointer to element -offsetof(bar, b) in this "array". This is of course not a correct index.

It would work if you had an "array" of bytes instead of the "array" of bar structures:

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



回答3:


You're moving your pointer back by too much, the offsetof b is sizeof(double), so probably 8, but the expression reinterpret_cast<bar*>(i) - offsetof(bar, b) moves it by sizeof(bar) * sizeof(double).

While it's technically legal to to cast a struct/class to it's 1st member, you should never need to do that, it can very easily lead to UB.




回答4:


If you just switch around the int and double member, so that the int member is first, then, because your class is standard-layout, you can simply reinterpret_cast to the struct and access the other members normally, because the first non-static data member and the class object will be 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;
}


来源:https://stackoverflow.com/questions/60335046/casting-from-member-pointer-to-whole-struct-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!