Search code examples
c++offsetof

Getting the offset of a member variable via casting a nullptr


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
}

Live on Coliru

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):

  1. Is the code perfectly legal, i.e. is it legal to "access" a member via a 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).
  2. 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?

Solution

    1. 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.

    1. 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.