I'm looking at the macro offsetof
from <cstddef>
, and saw that a possible implementation is via
#define my_offsetof(type, member) ((void*) &(((type*)nullptr)->member))
I tried it and indeed it works as expected
#include <iostream>
#define my_offsetof(type, member) ((void*) &(((type*)nullptr)->member))
struct S
{
char x;
short y;
int z;
};
int main()
{
std::cout << my_offsetof(S, x) << '\n';
std::cout << my_offsetof(S, y) << '\n';
std::cout << my_offsetof(S, z) << '\n';
S s;
std::cout << (void*) &((&s)->x) << '\n'; // no more relative offsets
std::cout << (void*) &((&s)->y) << '\n'; // no more relative offsets
std::cout << (void*) &((&s)->z) << '\n'; // no more relative offsets
}
the only modification I've done being that I use a final cast to void*
instead of size_t
, as I want to display the address as a pointer.
My question(s):
nullptr
, then take its address? If that's the case, then it seems that &(((type*)nullptr)->member)
computes the address of the member relative to 0, is this indeed the case? (it seems so, as in the last 3 lines I get the offsets relative to the address of s
). (void*)
from the macro definition, I get a segfault. Why? Shouldn't &(((type*)nullptr)->member)
be a pointer of type type*
, or is the type somehow erased here?
- Is the code perfectly legal?
No. It's undefined behavior. A compiler may choose to implement offsetof
in that manner, but that's because it is the implementation: it can choose how to implement its own features. You, on the other hand, do not get such "luxury."
There is no way for you to implement the offsetof
macro. Not in any standards-conforming manner.
- If I remove the final cast to (void*) from the macro definition, I get a segfault. Why? Shouldn't &(((type*)nullptr)->member) be a pointer of type type*, or is the type somehow erased here?
It's probably a segfault from trying to print my_offsetof(S, x)
(since x
is a char
and that expression results in char*
), because std::ostream
's operator<<
will try to print char*
as a C-style string.