What's the use of _ATL_PACKING constant when computing distance from the start of object?
Asked Answered
M

1

1

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?

Moan answered 16/2, 2011 at 8:21 Comment(0)
C
3

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.

Cadge answered 16/2, 2011 at 10:12 Comment(1)
@sharptooth: There's enough information in the class definitions themselves that the compiler can figure out their sizes and their offsets at compile time. The expression in offsetofclass macro evaluates to a constant, so the compiler should be able to fold the arithmetic.Cadge

© 2022 - 2024 — McMap. All rights reserved.