2

I came across following c++ code:

 #define OFFSETOF_MEMBER(t, f) \
  (reinterpret_cast<uintptr_t>(&reinterpret_cast<t*>(16)->f) - static_cast<uintptr_t>(16u)) // NOLINT

where t is a type, f is a field name. I wonder why can we put a integer 16 as parameter of reinterpret_cast.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Chao Wang
  • 21
  • 2
  • 1
    Note that this is Undefined Behavior. C++ does have `offset_of`, which is a macro defined by every implementation; some implementations will define it similar to this. – MSalters Jul 18 '18 at 06:55
  • ^ the macro is `offsetof` , and it's UB to use it on a non-standard-layout class – M.M Jul 23 '18 at 05:08

3 Answers3

1

16 is the address we're assigning to the pointer, which lets us calculate the offset of the specified member. The address of a pointer is just a number, so we can abuse this fact in order to get information about our structures/classes.

Say we have a struct:

struct point { 
    //Assuming 32-bit integer sizes. 
    //For 64-bit integersizes, 0x0, 0x8, 0x10 for the integer offsets
    int x; //Offset 0x0
    int y; //Offset 0x4
    int z; //Offset 0x8
}; static_assert(sizeof(point) == 12 /* or 0xC in hex */);

We use the macro:

OFFSETOF_MEMBER(point, y);

Expanding the macro, we get:

(reinterpret_cast<uintptr_t>(&reinterpret_cast<point*>(16)->y) - static_cast<uintptr_t>(16u)

Another way of expressing the reinterpret_cast<point*>(16)->y could be had like so: point * myPt = 16u; we know 16 is not a valid address, but the compiler doesn't, and so long as we don't try to read the address we're pointing to, we're okay.

Next, we can simplify all of &reinterpret_cast<point*>(16)->y to: &myPt->y. We know from above that y is @ offset 0x4, and since myPt is 16: 16 + 0x4 = 20

Then we have reinterpret_cast<uintptr_t>(20u) - static_cast<uintptr_t(16u) or 20 - 16, which gives us the offset of y, that is, 0x4.

Zoru22
  • 21
  • 1
  • 4
0

From the reference:

3) A value of any integral or enumeration type can be converted to a pointer type. [...]

So, reinterpret_cast<> is partly designed to do exactly that.

Jodocus
  • 7,493
  • 1
  • 29
  • 45
0

Integer 16 is just a memory address. Expression reinterpret_cast<t*>(16) simply means "interpret the object at address 16 as type t", but you know that there's no such t object at the address. Theoretically, 16 could be replaced by any 4x (32bit) or 8x (64bit) integer. If you choose 0, the macro can be simplified as:

#define OFFSETOF_MEMBER(t, f) \
  (reinterpret_cast<uintptr_t>(&reinterpret_cast<t*>(0)->f))

See offsetof for more information.

Adam Gu
  • 166
  • 2
  • 5
  • 2
    But note that this code is deep into undefined behavior. The reason that `offset_of` is defined in the standard library is that the standard library ships with a compiler, and can take advantage of known behavior of that compiler. It's not an example of good, portable code. – Pete Becker Jul 18 '18 at 12:27