问题
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