ATL features a set of macros for so-called COM maps. COM map is a table that associates an interface GUID with an offset that is to be added to this
pointer to get to the corresponding subobject - the whole stuff works as replacement to explicit static_cast
for the upcast inside IUnknown::QueryInterface()
.
The map entries are built by using offsetofclass
macro:
#define _ATL_PACKING 8
#define offsetofclass(base, derived)\
((DWORD_PTR)(static_cast<base*>((derived*)_ATL_PACKING))-_ATL_PACKING)
which I'll rewrite as the following pseudocode "function" for readability in this question:
derived* derivedPointer = (derived*)_ATL_PACKING;
base* basePointer = static_cast<base*>(derivedPointer);
DWORD_PTR offset = (DWORD_PTR)(basePointer)-_ATL_PACKING;
Looks reasonable - it obtains a pointer to an imaginary derived object, then does an explicit static_cast
to shift the pointer, then computes distance between those imaginary objects.
The question is why is the constant 8 there? Why do we need that constant and why is it chosen to be 8?
The non-zero constant is there because the macro does not work with null pointers. We know that the value of a null pointer is a null pointer constant which evaluates to 0
:
C++ Standard 4.10/1 Pointer conversions [conv.ptr]:
A null pointer constant is an integral
constant expression (5.19) rvalue of
integer type that evaluates to zero. A
null pointer constant can be converted
to a pointer type; the result is the
null pointer value of that type and is
distinguishable from every other value
of pointer to object or pointer to
function type....
This is the relevant clause with respect to converting from a derived class to a base class pointer type:
C++ Standard 4.10/3 Pointer conversions [conv.ptr]:
An rvalue of type “pointer to cv D,”
where D is a class type, can be
converted to an rvalue of type
“pointer to cv B,” where B is a base
class (clause 10) of D. If B is an
inaccessible (clause 11) or ambiguous
(10.2) base class of D, a program that
necessitates this conversion is
ill-formed. The result of the
conversion is a pointer to the base
class sub-object of the derived class
object. The null pointer value is
converted to the null pointer value of
the destination type.
Basically null pointers prevent pointer arithmetic from kicking in during a derived-to-base conversion; you will just get another null pointer. The arithmetic is used to "fix" non-null pointers during such conversions so that it points to the proper subobject. The offsetofclass
macro depends on this arithmetic to determine the offset.
The number 8
used is arbitrary. You could've used any number there like 1
or 4
, as long as it wasn't zero.