Пример из реализации MSVC:
#define offsetof(s,m) \
(size_t)&reinterpret_cast<const volatile char&>((((s *)0)->m))
// ^^^^^^^^^^^
Как можно видеть, он разделяет нулевой указатель, который обычно вызывает поведение undefined. Является ли это исключением из правила или того, что происходит?