Empty bases layout closure

Daveed Vandevoorde daveed at edg.com
Wed Jul 7 18:03:32 UTC 1999


The C++ ABI issues currently contains:

	[990617 All] At offset zero is the Vptr whenever there is one, as well as
	the primary base class if any (see A-7). Also at offset zero is any number
	of empty base classes, as long as that does not place multiple subobjects
	of the same type at the same offset. If there are multiple empty base 
	classes such that placing two of them at offset zero would violate this
	constraint, the first is placed there. (First means in declaration order.) 

	All other non-virtual base classes are laid out in declaration order at
	the beginning of the class. All other virtual base subobjects will be
	allocated at the end of the class, left-to-right, depth-first. 

	The above ignores issues of padding for alignment, and possible reordering
	of class members to fit in padding areas. See issue A-9.

I am interested in resolving the issue of what to do with empty bases that cannot
be located at the object's origin (because of the type conflict).

Empty classes appear in a complete object's layout in one of the following ways:
	(a) a nonvirtual direct base
	(b) a virtual base
	(c) a direct member
	(d) a nondirect member or nondirect nonvirtual base
Cases (a-c) can be laid out specifically for the complete object, while (d) is 
determined by the direct subobject that contains the (d) case.  (d) is none-
theless important because of the type constraint that two empty subobjects of
the same type should not be allocated at the same address.  I'm leaving case (b)
out of this discussion and assume that its resolution as it stands today is fine,
(but subordinate to prior layout decisions).

An example:

	struct E1 {};
	struct E2: E1 {};
	struct E3 {};
	struct E4: E3 {};
	struct N1 { E1 n1; }

	struct D: E1, E2, N1, E3, E4 {
	  E3 e3;
	};

D could be laid out in many ways. An optimal layout could be:
	this+0:	base E1, base E3
	this+1: base E2, member e3
	this+2: base N1, base E4
However, an algorithm that would reliably generate such optimal layouts is 
likely hard to describe.

I also think there is value in decribing the layout in terms of an algorithm
instead of trying to describe the results of that algorithm.

Here are three general approaches to the empty base layout algorithm:

(1) No reordering at all:
An empty base can take up zero bytes, but all direct bases and members are 
allocated in declaration order.  If allocating an element would create a type 
conflict with a previously allocated empty base, move to the next alignment 
slot.  This is not compatible with our issues list so far. For the above 
example, it leads to:
	this+0:	base E1
	this+1:	base E2 (shift since conflict)
	this+2:	base N1 (shift since conflict with E2::E1)
	this+3:	base E3 (previous was not empty)
	this+4:	base E4	(shift since conflict)
	this+5:	member e3 (shift since conflict with E4::E3)
Not great, but the example is quite artificial.

(2) Reorder only to origin:
Same as (1) but if an empty base to be allocated can be allocated at offset
zero with size zero this is done (in declaration order).  This is more or
less compatible with what the issues list says so far, except it might be
read to say that if this fails once, it is never reattempted again.  With 
the latter constraint the layout of the example would be identical to (1).  
Without that constraint, you get:
	this+0:	base E1, base E3
	this+1:	base E2
	this+2:	base N1
	this+3:	base E4
	this+4:	member e4

(3) Layout empty bases in second pass:
There are various subalternatives in this option: the pass can be inserted
before or after the layout of direct members; the location granularity can
be a byte or the boundaries of already allocated subobjects; and there are
probably other tweaks that can be made.  In our example the granularity
doesn't matter since no subobject is larger than a byte.  Assuming that the
pass occurs after direct member layout, you get:
	this+0:	base N1
	this+1:	member e3, base E1
	this+2:	base E2, base E3
	this+3: base E4

Comments?  Preferences?

My order of preference is:
	(1) As for other decisions, this option has the merits of being
	    simple and influencable by the knowledgeable programmer.
	(3 with no intrasubobject allocation)
	    I also prefer to have the second pass occur after direct member
	    layout to increase opportunities (including opportunities that
	    could not be emulated by the knowledgeable user of scheme (1)).
	(2) I anticipate the added specification complexity may not be worth
	    potential gains.
	(3 with intrasubobject allocation; i.e. byte-granularity)
	    This is a form of interleaving which I think was already unpopular
	    when we discussed the allocation of different access-sections.

	Daveed




More information about the cxx-abi-dev mailing list