2

In looking at how my compiler (VS2015) implements the offsetof macro, I see the following implementation:

#define offsetof(s,m) ((size_t)&reinterpret_cast<char const volatile&>((((s*)0)->m)))

It appears to me that this is dereferencing a null pointer at ((s*)0)->m. I understand this to be undefined behavior. Is this undefined behavior that the compiler specifies more strongly than the language, or is there some nuance to the "never dereference a null pointer" rule that I don't understand?

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
Graznarak
  • 3,626
  • 4
  • 28
  • 47
  • 3
    The Standard says that `offsetof` does what it says it does. That's all you need to know. – Kerrek SB Aug 31 '16 at 22:42
  • 1
    The implementation can do whatever it wants as long as it works (that is, does what the standard says it should). UB applies to user code only. – Baum mit Augen Aug 31 '16 at 22:43
  • @KerrekSB I understand that `offsetof` is specified in the standard, so it will just work if the implementation is supported by the compiler. What I want to know is if it is valid for me to write similar code and expect it to work. – Graznarak Aug 31 '16 at 22:44
  • 3
    It's not dereferencing a null pointer. It's casting 0 to a pointer t s and taking then dereferencing the m memer of s, then casting it to `char const volatile&` and then taking the address of that (&) and casting it to size_t. In simpler terms, it asks "If an object of type s, was at address 0, what would the address of member m be (because that would be the offset of m in s)". Due to some nuances, it's implementation defined if this'll work. Obviously it will for VS2015 since they're using it. – Petr Skocik Aug 31 '16 at 22:44
  • 2
    @Graznarak No, you can't. – Baum mit Augen Aug 31 '16 at 22:45
  • A simpler example: `char *cptr; printf("%zu\n", sizeof(*cptr));` is not derefencing an undefined variable. It's not dereferencing anything. – Weather Vane Aug 31 '16 at 22:52
  • 1
    @WeatherVane `sizeof` is an unevaluated context. The content is never evaluated so there is no undefined behavior as far as dereferencing the null pointer. – Graznarak Aug 31 '16 at 22:56
  • @Graznarak `offsetof` is similar. It's a compiler evaluation, not at runtime. – Weather Vane Aug 31 '16 at 22:57
  • 1
    @WeatherVane `offsetof` is not an unevaluated context. It is a standard defined macro that returns a `size_t` that is the offset of the member relative to the beginning of the type. – Graznarak Aug 31 '16 at 22:59
  • A macro is not a function. It is evaluated at compile time. – Weather Vane Aug 31 '16 at 22:59
  • A macro essentially does a text substitution. So, I guess that you could say that a macro is not evaluated at runtime, but it just does a text substitution, so the substituted text is still evaluated. – Graznarak Aug 31 '16 at 23:01
  • @Graznarak the offset is known at compile time. There is nothing to dereference. – Weather Vane Aug 31 '16 at 23:02
  • 1
    @WeatherVane: You're missing the point. `sizeof(*static_cast(nullptr))` does not dereference a null pointer because the expression is not evaluated. This is called an unevaluated context. Obviously, just `*static_cast(nullptr)` would be evaluated, and it would be undefined behavior for dereferencing a null pointer. The `offsetof` macro provided is *not* an unevaluated context; after macro text substitution, the compiler *will* evaluate the result; the fact that its result is always the same has nothing to do with whether or not it's evaluated. – GManNickG Aug 31 '16 at 23:09
  • The question is fair: no, we are not allowed to dereference null because the behavior is undefined, full stop. The compiler writers, of course, can peek into what undefined *really* means and do whatever they want. As far as we are concerned, dereferencing a null pointer reformats the entire disk, unless it occurs in this exact expression. Obviously this is a strange implementation of undefined behavior, and in reality there *are* guarantees about dereferencing null as is done here, but we aren't privy to what these are. (1/2) – GManNickG Aug 31 '16 at 23:13
  • @GManNickG why the C tag? – Weather Vane Aug 31 '16 at 23:13
  • It's free to fail on other compilers, for example, where `offsetof` is implemented differently because they don't give the same 'undefined behavior' behavior. (2/2) – GManNickG Aug 31 '16 at 23:13
  • @WeatherVane: I didn't add the tag. – GManNickG Aug 31 '16 at 23:14
  • Before I first commented I tried to give a type cast of `*NULL` to `sizeof` in a C program: MSVC would not have it, which why I skewed my first comment. – Weather Vane Aug 31 '16 at 23:16
  • Is `*NULL` dereferencing a null _`void *`_? You can't dereference any `void *` (unless, perhaps, you're using GCC or a compiler compatible with it). If you converted it to `*(int *)NULL`, say, it might have worked. Or it might still object. – Jonathan Leffler Aug 31 '16 at 23:18
  • I was trying it with a `struct` pointer before. I just tried again with this `printf("%zu\n", sizeof(*(char*)NULL));` which works. But I still can't understand the differnce with finding the offset of a `struct` member which is known at compile time. However it was not my question! – Weather Vane Aug 31 '16 at 23:19
  • Yes, the code in that particular definition of `offsetof` has undefined behavior if it's evaluated. That just means the behavior is not defined by the standard. The implementation is free to use it, as long as *the implementation* can guarantee that the actual behavior matches what the standard requires for `offsetof`. (If that particular definition of `offsetof` didn't work, the implementation would have to define it differently.) – Keith Thompson Aug 31 '16 at 23:28
  • @PSkocik casting `0` to a pointer is defined as producing a null pointer. The `->` operator is defined as dereferencing its left-hand operand . (You claim it dereferences the right-hand operand, which makes no sense) – M.M Sep 01 '16 at 00:09
  • @WeatherVane The C tag is because offsetof is a macro defined in the C language. It turns out that this implementation uses a C++ cast instead of a C cast. – Graznarak Sep 01 '16 at 13:51

0 Answers0