[C++ ABI] HP's object layout
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[C++ ABI] HP's object layout



Attached are two HTML files that describe HP's object and vtable layout. 
We intend to modify this somewhat for IA-64, but this is our starting 
point. (The third attachment, vtables1.txt, is referenced by 
vtables.html.)

Cary Coutant
408-447-5759
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<html>
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
   <meta name="GENERATOR" content="Mozilla/4.5 [en] (X11; I; HP-UX B.10.20 9000/715) [Netscape]">
</head>
<body>

<h1>
<font face="Arial,Helvetica">The Layout of Objects in aCC</font></h1>

<p><br>This is a brief note outlining how objects are laid out by aCC.
The contents of this page should supersede prior Taligent information.
This page does not describe the layout of virtual tables or the construction
process for objects (coming soon to a page near you).
<h2>
<font face="Arial,Helvetica"><font size=+2>Single inheritance</font></font></h2>

<p><br>Classes not deriving from base-classes simply layout their members
in declaration order. Although ANSI allows us to reorder protection sections
(private, protected, public), aCC ignores protection directives for layout
purposes. The following program illustrates this:
<ul>
<pre>extern "C" int printf(char const*, ...);

struct A {
&nbsp;&nbsp; int a1;
private:
&nbsp;&nbsp; friend int main();
&nbsp;&nbsp; int a2;
public:
&nbsp;&nbsp; int a3;
};

int main() {
&nbsp;&nbsp; A obj;
&nbsp;&nbsp; printf("obj.a1@%x&nbsp; obj.a2@%x&nbsp; obj.a3@%x\n",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;obj.a1, &amp;obj.a2, &amp;obj.a3);
}</pre>
</ul>
Which e.g. could output:
<ul>
<pre>obj.a1@7b03ad80&nbsp; obj.a2@7b03ad84&nbsp; obj.a3@7b03ad88</pre>
</ul>
If a class D is derived from a class B, the members of B come before those
added by the D-derivation.
<p>If D or B has a virtual member function, a pointer to a virtual table
is placed as a first ``hidden member'' in the object. This pointer is followed
by the members of B, which in turn are followed by the members of D.
<p>If B is a virtual base class of D, a pointer to a virtual table is also
inserted as a first ``hidden member'', and the members of the virtual base
will follow those added by D. This virtual table describes the location
of the virtual base within the D object; this location depends on whether
the D object is complete, or whether it is part of a larger object (see
the multiple inheritance case below).
<p>If in the latter case, the B object is polymorphic this virtual base
object must know its location within the larger object and therefore contains
a second hidden member as the first item in the list of virtual base members.
This is not the case if only the D derivation introduces polymorphism.
<p>The following program must be linked with the -E linker option (aCC
-Wl,-E); it prints out a description of a variety of D:B objects.
<ul>
<pre>#include &lt;assert.h>
#include &lt;dl.h>
#include &lt;malloc.h>
#include &lt;stdio.h>
#include &lt;typeinfo>

typedef void*(*shl_allocator)();

shl_symbol *sym_table;
int n_syms;

static void
load_self_symbols() {
&nbsp;&nbsp; shl_descriptor *d = 0;
&nbsp;&nbsp; shl_gethandle(PROG_HANDLE, &amp;d);
&nbsp;&nbsp; printf("Executable file %s\n", d->filename);
&nbsp;&nbsp; shl_t h = d->handle;
&nbsp;&nbsp; n_syms = shl_getsymbols(h, TYPE_DATA, EXPORT_SYMBOLS,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (shl_allocator)malloc, &amp;sym_table);
}

static char const*
address2sym(void const *a) {
&nbsp;&nbsp; if (!a)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return "&lt;null pointer>";
&nbsp;&nbsp; int offset = 0x3fff, closest = -1;
&nbsp;&nbsp; for (int k = 0; k!=n_syms; ++k) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int diff = (char const*)a-(char const*)sym_table[k].value;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (diff==0)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return sym_table[k].name;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if (diff>0 &amp;&amp; diff&lt;offset) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closest = k;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; offset = diff;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; }
&nbsp;&nbsp; static char msg[80];
&nbsp;&nbsp; if (closest==-1)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sprintf(msg, "&lt;address %x not found>", a);
&nbsp;&nbsp; else
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sprintf(msg, "%s + %d", sym_table[closest].name, offset);
&nbsp;&nbsp; return msg;
}


struct B {
&nbsp;&nbsp; long b;
};

struct pB {
&nbsp;&nbsp; long b;
&nbsp;&nbsp; virtual void f() {}
};

struct D: B {
&nbsp;&nbsp; long d;
} D_obj;

struct pD: B {
&nbsp;&nbsp; long d;
&nbsp;&nbsp; virtual void f() {}
} pD_obj;

struct Dp: pB {
&nbsp;&nbsp; long d;
} Dp_obj;

struct pDp: pB {
&nbsp;&nbsp; long d;
&nbsp;&nbsp; virtual void f() {}
} pDp_obj;

struct Dv: virtual B {
&nbsp;&nbsp; long d;
} Dv_obj;

struct Dvp: virtual pB {
&nbsp;&nbsp; long d;
} Dvp_obj;

struct pDv: virtual B {
&nbsp;&nbsp; long d;
&nbsp;&nbsp; virtual void f() {}
} pDv_obj;

template&lt;typename T>
void describe(T&amp; obj) {
&nbsp;&nbsp; printf("%s object: size==%d, address==%x\n",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; typeid(T).name(), sizeof(T), &amp;obj);
&nbsp;&nbsp; for (int offset = 0; offset&lt;sizeof(T); offset+=sizeof(long)) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char const *p = (char const*)&amp;obj+offset;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (p==(char const*)&amp;obj.b)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("object.b\n");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if (p==(char const*)&amp;obj.d)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("object.d\n");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("pointer to: %s\n", address2sym(*(void**)p));
&nbsp;&nbsp; }
&nbsp;&nbsp; printf("\n");
}

int main() {
&nbsp;&nbsp; assert(sizeof(void*)==sizeof(long));
&nbsp;&nbsp; load_self_symbols();
&nbsp;&nbsp; describe(D_obj);
&nbsp;&nbsp; describe(pD_obj);
&nbsp;&nbsp; describe(Dp_obj);
&nbsp;&nbsp; describe(pDp_obj);
&nbsp;&nbsp; describe(Dv_obj);
&nbsp;&nbsp; describe(Dvp_obj);
&nbsp;&nbsp; describe(pDv_obj);
}</pre>

<pre></pre>
</ul>

<h2>
<font face="Arial,Helvetica"><font size=+2>Multiple Inheritance</font></font></h2>

<p><br>Assume a class D declares multiple base classes B1, B2, ..., Bn
(in that order). The ``primary base-class of D'' is defined as follows:
<ol>
<li>
If any non-virtual base Bk has virtual base-classes itself, the first such
Bk will be the primary base class of D; otherwise</li>

<li>
If any non-virtual base Bk has or inherits a virtual function, the first
such Bk will the primary base class of D; otherwise</li>

<li>
There is no primary base class.</li>
</ol>
If D has a primary base-class, the non-virtual part of the corresponding
sub-object will come first in the layout. Furthermore, the complete object,
and its primary base subobject will share a virtual table pointer. E.g.:
<ul>
<pre>struct nB { // non-polymorphic
&nbsp;&nbsp; long n;
};

struct pB { // polymorphic
&nbsp;&nbsp; virtual void f();
&nbsp;&nbsp; long p;
};

struct wB: virtual nB { // with virtual base
&nbsp;&nbsp; long w;
};

struct D: nB, pB, wB { // primary-base is wB by (1) above
&nbsp;&nbsp; long d;
};</pre>
</ul>
The primary base of D is wB (by (1) above) and hence its data-members and
nonvirtual subobjects will come first in the layout:
<ul><tt>word 1: vtbl-pointer for D and wB-in-D (shared)</tt>
<br><tt>word 2: wB::w</tt>
<br><tt>// ...</tt></ul>
Next come the remaining non-virtual base classes in the order they were
declared, followed by the data-members of D. If any of these virtual base-classes
had virtual members or virtual base-classes, it would have a leading virtual-table
pointer of its own. For the example above:
<ul><tt>word 1: vtbl-pointer for D and wB-in-D (shared)</tt>
<br><tt>word 2: D::wB::w</tt>
<br><tt>word 3: D::nB::n // nB needs no vtable-pointer</tt>
<br><tt>word 4: vtbl-pointer for pB (not shared)</tt>
<br><tt>word 5: D::pB::p</tt>
<br><tt>word 6: D::d</tt>
<br><tt>// ...</tt></ul>
Finally, the direct and indirect virtual base sub-objects are laid out.
They are placed in the order of a depth-first, left-to-right traversal
of the inheritance graph (independently of when the traversal hits the
primary base). `Left-to-right' means the order of declaration of the bases.
The complete layout of the example above is hence:
<ul><tt>word 1: vtbl-pointer for D and wB-in-D (shared)</tt>
<br><tt>word 2: D::wB::w</tt>
<br><tt>word 3: D::nB::n // nB needs no vtable-pointer</tt>
<br><tt>word 4: vtbl-pointer for pB (not shared)</tt>
<br><tt>word 5: D::pB::p</tt>
<br><tt>word 6: D::d</tt>
<br><tt>word 7: D::wB::nB::n // virtual-base subobject</tt></ul>
One more (involved) example:
<pre>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; B_1&nbsp;&nbsp;&nbsp; B_2*&nbsp;&nbsp;&nbsp;&nbsp; B_4*&nbsp;&nbsp;&nbsp;&nbsp; B_7
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /v&nbsp; \v&nbsp;&nbsp;&nbsp;&nbsp; |
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp; B_3&nbsp;&nbsp; B_5&nbsp;&nbsp; B_6&nbsp;&nbsp;&nbsp; /
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp; /
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;&nbsp; /
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp; /&nbsp;&nbsp; /
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp; |v&nbsp; /v&nbsp; /
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp; `- D*-'&nbsp;&nbsp; /v
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; `-----' `----'</pre>
`v' indicates virtual inheritance. Assume that B_2, B_4 and D have virtual
member functions (marked with a `*'). Here are the declarations:
<pre>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct B_1 { long b1; };

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct B_2 {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; long b2;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct B_3: B_2 { long b3; };

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct B_4 {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; long b4;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct B_5: virtual B_4 { long b5; };

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct B_6: virtual B_4 { long b6; };

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct B_7 { long b7; };

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct D: B_1, B_3,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual B_5, virtual B_6, virtual B_7 {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; long d;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };</pre>
B_3 is the primary base of D by definition (2) above. The depth-first,
left-to-right traversal of the virtual bases is: B_4, B_5, B_6, B_7 (the
base-classes were numbered in depth- first left-to-right order). By the
discussion above, a complete object of type D is laid out as follows:
<ul><tt>primary-base:</tt>
<br><tt>&nbsp;&nbsp;&nbsp; word 1: vtable-pointer for D and B_3-in-D (shared)</tt>
<br><tt>&nbsp;&nbsp;&nbsp; word 2: b2</tt>
<br><tt>&nbsp;&nbsp;&nbsp; word 3: b3</tt>
<br><tt>other non-virtual bases:</tt>
<br><tt>&nbsp;&nbsp;&nbsp; word 4: b1</tt>
<br><tt>data-members:</tt>
<br><tt>&nbsp;&nbsp;&nbsp; word 5: d</tt>
<br><tt>virtual bases:</tt>
<br><tt>&nbsp;&nbsp;&nbsp; word 6: vtable-pointer for B_4</tt>
<br><tt>&nbsp;&nbsp;&nbsp; word 7: b4</tt>
<br><tt>&nbsp;&nbsp;&nbsp; word 8: vtable-pointer for B_5</tt>
<br><tt>&nbsp;&nbsp;&nbsp; word 9: b5</tt>
<br><tt>&nbsp;&nbsp;&nbsp; word 10: vtable-pointer for B_6</tt>
<br><tt>&nbsp;&nbsp;&nbsp; word 11: b6</tt>
<br><tt>&nbsp;&nbsp;&nbsp; word 12: b7</tt></ul>
Note that virtual base classes never share a vtable-pointer. Hereunder
is a program that demonstrates this last example along with a few simpler
examples:
<ul>
<pre>#include &lt;assert.h>
#include &lt;dl.h>
#include &lt;malloc.h>
#include &lt;stdio.h>
#include &lt;typeinfo>

typedef void*(*shl_allocator)();

shl_symbol *sym_table;
int n_syms;

static void
load_self_symbols() {
&nbsp;&nbsp; shl_descriptor *d = 0;
&nbsp;&nbsp; shl_gethandle(PROG_HANDLE, &amp;d);
&nbsp;&nbsp; printf("Executable file %s\n", d->filename);
&nbsp;&nbsp; shl_t h = d->handle;
&nbsp;&nbsp; n_syms = shl_getsymbols(h, TYPE_DATA, EXPORT_SYMBOLS,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (shl_allocator)malloc, &amp;sym_table);
}

static char const*
address2sym(void const *a) {
&nbsp;&nbsp; if (!a)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return "&lt;null pointer>";
&nbsp;&nbsp; int offset = 0x3fff, closest = -1;
&nbsp;&nbsp; for (int k = 0; k!=n_syms; ++k) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int diff = (char const*)a-(char const*)sym_table[k].value;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (diff==0)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return sym_table[k].name;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if (diff>0 &amp;&amp; diff&lt;offset) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closest = k;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; offset = diff;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; }
&nbsp;&nbsp; static char msg[80];
&nbsp;&nbsp; if (closest==-1)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sprintf(msg, "&lt;address %x not found>", a);
&nbsp;&nbsp; else
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sprintf(msg, "%s + %d", sym_table[closest].name, offset);
&nbsp;&nbsp; return msg;
}


struct nB {
&nbsp;&nbsp; nB(): b('b') {}
&nbsp;&nbsp; long b;
};

struct pB {
&nbsp;&nbsp; pB(): p('p') {}
&nbsp;&nbsp; long p;
&nbsp;&nbsp; virtual void f() {}
};

struct vB {
&nbsp;&nbsp; vB(): v('v') {}
&nbsp;&nbsp; long v;
};

struct wB: virtual nB {
&nbsp;&nbsp; wB(): w('w') {}
&nbsp;&nbsp; long w;
};

struct xB: virtual nB {
&nbsp;&nbsp; xB(): x('x') {}
&nbsp;&nbsp; long x;
};

struct cB: virtual nB {
&nbsp;&nbsp; cB(): c('c') {}
&nbsp;&nbsp; long c;
&nbsp;&nbsp; virtual void f() {}
};

struct Dnv: nB, virtual vB {
&nbsp;&nbsp; Dnv(): d('d') {}
&nbsp;&nbsp; long d;
} Dnv_obj;

struct Dvn: virtual vB, nB {
&nbsp;&nbsp; Dvn(): d('d') {}
&nbsp;&nbsp; long d;
} Dvn_obj;

struct Dvw: virtual vB, virtual wB {
&nbsp;&nbsp; Dvw(): d('d') {}
&nbsp;&nbsp; long d;
} Dvw_obj;

struct Dwv: virtual wB, virtual vB {
&nbsp;&nbsp; Dwv(): d('d') {}
&nbsp;&nbsp; long d;
} Dwv_obj;

struct Dwx: virtual wB, virtual xB {
&nbsp;&nbsp; Dwx(): d('d') {}
&nbsp;&nbsp; long d;
} Dwx_obj;

struct Dwc: wB, virtual cB {
&nbsp;&nbsp; Dwc(): d('d') {}
&nbsp;&nbsp; long d;
} Dwc_obj;


template&lt;typename T>
void describe(T&amp; obj) {
&nbsp;&nbsp; printf("%s object: size==%d, address==%x\n",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; typeid(T).name(), sizeof(T), &amp;obj);
&nbsp;&nbsp; for (int offset = 0; offset&lt;sizeof(T); offset+=sizeof(long)) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char const *p = (char const*)&amp;obj+offset;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (*(long const*)p&lt;='z')
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("object.%c\n", char(*(long const*)p));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("pointer to: %s\n", address2sym(*(void**)p));
&nbsp;&nbsp; }
&nbsp;&nbsp; printf("\n");
}

struct B_1 {
&nbsp;&nbsp; B_1(): b1('1') {}
&nbsp;&nbsp; long b1;
};

struct B_3 {
&nbsp;&nbsp; B_3(): b3('3') {}
&nbsp;&nbsp; long b3;
&nbsp;&nbsp; virtual void f() {}
};

struct B_2: B_3 {
&nbsp;&nbsp; B_2(): b2('2') {}
&nbsp;&nbsp; long b2;
};

struct B_5 {
&nbsp;&nbsp; B_5(): b5('5') {}
&nbsp;&nbsp; long b5;
&nbsp;&nbsp; virtual void f() {}
};

struct B_4: virtual B_5 {
&nbsp;&nbsp; B_4(): b4('4') {}
&nbsp;&nbsp; long b4;
};

struct B_6: virtual B_5 {
&nbsp;&nbsp; B_6(): b6('6') {}
&nbsp;&nbsp; long b6;
};

struct B_7 {
&nbsp;&nbsp; B_7(): b7('7') {}
&nbsp;&nbsp; long b7;
};

struct D: B_1, B_2,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual B_4, virtual B_6, virtual B_7 {
&nbsp;&nbsp; D(): d('d') {}
&nbsp;&nbsp; long d;
&nbsp;&nbsp; virtual void f() {}
} complex_D_object;

int main() {
&nbsp;&nbsp; assert(sizeof(void*)==sizeof(long));
&nbsp;&nbsp; load_self_symbols();
&nbsp;&nbsp; describe(Dnv_obj);
&nbsp;&nbsp; describe(Dvn_obj);
&nbsp;&nbsp; describe(Dvw_obj);
&nbsp;&nbsp; describe(Dwv_obj);
&nbsp;&nbsp; describe(Dwx_obj);
&nbsp;&nbsp; describe(Dwc_obj);
&nbsp;&nbsp; describe(complex_D_object);
}


</pre>
</ul>

<h2>
<font face="Arial,Helvetica">The empty base-class bug</font></h2>

<p><br>The C++ language allows an empty base-class to share its location
with its descendants. E.g.:
<ul>
<pre>struct Empty {} empty;

struct AlsoEmpty: Empty {} also_empty;

struct NotEmpty: AlsoEmpty {
&nbsp;&nbsp; long d;
} not_empty;

struct Empty2 {};

struct Bug: Empty2, AlsoEmpty {
&nbsp;&nbsp; long d;
} bug;</pre>
</ul>
In this example, the language allows the following equalities:
<ul>
<pre>(void*)&amp;(Empty&amp;)also_empty == (void*)&amp;also_empty
(void*)&amp;(Empty&amp;)(AlsoEmpty&amp;)not_empty
&nbsp;&nbsp; == (void*)&amp;(AlsoEmpty&amp;)not_empty
&nbsp;&nbsp; == (void*)&amp;not_empty
(void*)&amp;(Empty&amp;)(AlsoEmpty&amp;)bug
&nbsp;&nbsp; == (void*)&amp;(AlsoEmpty&amp;)bug
&nbsp;&nbsp; == (void*)&amp;bug</pre>
</ul>
but also imposes the following INequalities:
<pre>(void*)&amp;empty != (void*)&amp;also_empty
(void*)&amp;(Empty2&amp;)bug != (void*)&amp;(AlsoEmpty&amp;)bug</pre>
I.e., for two subobjects to have the same address, one must be derived
from the other (a sibling-relationship is not valid). aCC violates this
language rule by indiscriminately allocating zero bytes for empty base-classes.
The following program illustrates how this leads to a violation of the
last inequality above.
<ul>
<pre>#include &lt;stdio.h>

struct Empty {};

struct Empty2 {};

struct AlsoEmpty: Empty {} also_empty;

struct Bug: Empty2, AlsoEmpty {
&nbsp;&nbsp; long d;
} bug;

int main() {
&nbsp;&nbsp; printf("&amp;bug == %x\n&amp;(Empty2&amp;)bug == %x\n&amp;(AlsoEmpty&amp;)bug == %x\n",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;bug, &amp;(Empty2&amp;)bug, &amp;(AlsoEmpty&amp;)bug);
}</pre>
</ul>

</body>
</html>
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<html>
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
   <meta name="GENERATOR" content="Mozilla/4.5 [en] (X11; I; HP-UX B.10.20 9000/715) [Netscape]">
</head>
<body>

<h1>
<font face="Arial,Helvetica">Virtual Tables in aCC</font></h1>

<p><br>This note describes the organization of <i>virtual tables</i> (or
<i>vtables</i>)
in aCC. These tables are used by the aCC object model for a variety of
purposes:
<ol>
<li>
Dispatching virtual function calls</li>

<li>
Run-time type identification (RTTI) and dynamic casting</li>

<li>
Location of virtual base classes</li>
</ol>
Each entry in a virtual table has the size of a <tt>long int</tt> and can
contain either an integer or a pointer.
<p>Pointers to these tables are stored in objects as described in <i><a href="http://cllweb.cup.hp.com/acxx/aCC/Documentation/objectlayout.html";>The
Layout of Objects in aCC</a></i>. In general, these pointers can point
to the middle of a table: we'll call this the <i>origin</i> of the table.
Entries at negative offsets of such an origin describe the layout of virtual
bases in an object, while entries at non-negative offsets relate to virtual
functions and type-information. The information at negative offsets of
the origin of a table thus describes is relatively static since it the
same throughout the life-time of an object: it is determined by the type
of the complete object and does not change during construction and destruction.
This is different from the information at non-negative offsets of the origin
of a table which describes the dynamic identity of an object: this identity
changes as the various parts of a complex object are constructed and destroyed.
That ``change'' is achieved by having the object point to a sequence of
virtual tables as it is constructed (and the reversed sequence as it is
destroyed). This sequence of tables is maintained in another static table
called <i>table of virtual tables</i> or <i>VLDVTT</i>.
<p>Every complete object with virtual bases or virtual functions has at
least one pointer to a virtual table located in the first word of that
object: the <i>primary vtable</i> pointer. Other pointers to <i>secondary
vtables</i> may also appear at positive offsets in the object if the object
uses multiple inheritance or virtual bases.
<h2>
<font face="Arial,Helvetica">Identity Section</font></h2>

<p><br>The entries at non-negative offsets of the origin of a virtual table
form the so-called <i>identity section</i> of that table. These entries
are structured as follows:
<ul><tt>origin + 0: zero (meant to be the meta-vtable pointer)</tt>
<br><tt>origin + 1: offset from origin of complete object to this subobject
(non-negative)</tt>
<br><tt>origin + 2: pointer to std::type_info object</tt>
<br><tt>origin + 3: pointer to duplicate and inaccessible base information</tt>
<br><tt>origin + 4: plabel to virtual function or adjustor thunk</tt>
<br><tt>...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : more such plabels</tt>
<br><tt>origin + N: zero</tt></ul>
The original design of aCC meant to have the first entry of the virtual
table point to a structure that would describe that table itself; however,
this was not implemented and instead this entry is zero.
<p>The second through fourth entry are used for RTTI. When applying the
C++ <tt>typeid</tt> operator to a polymorphic object, the <tt>std::type_info</tt>
object pointed to by the third entry of the virtual table (<tt>origin +
2</tt>) is returned. This <tt>type_info</tt> object also embeds a pointer
to a table describing the base-classes for that type, which is useful both
for the <tt>dynamic_cast</tt> operator and for catching exceptions. The
base-class table and <tt>type_info</tt> structures are described elsewhere.
<p>As it turns out, the base-class table and the <tt>type_info</tt> structures
are not sufficient to implement the <tt>dynamic_cast</tt> functionality
as required by the language definition. In particular, an additional mechanism
is needed to find the origin of the complete object (in <tt>dynamic_cast&lt;void*></tt>
expressions) and another one to handle duplicate or inaccessible bases
classes (see further). The second entry provides a straightforward way
to recover the origin of an object, while the entry at <tt>origin + 3</tt>
points to a structure that allows handling casting from/to duplicate or
inaccessible bases.
<p>Except for that last entry which is always zero, the remainder of the
vtable contains plabels (pointers to the procedure, or if bit 2^1 is set,
to a linkage table entry of that procedure) for the virtual functions defined
or inherited by the class pointing to this virtual table. In the case of
multiple inheritance where an inherited virtual function assumes a <tt>this</tt>
parameter that points to another part of the object, the adjustment of
the <tt>this</tt>-pointer is done by a stub called an <i>adjustor thunk</i>:
in that case, the plabel for that virtual function will point to that thunk
rather than to the virtual function code itself.
<p>For example:
<ul><tt>struct A {</tt>
<br><tt>&nbsp;&nbsp; virtual void a1();</tt>
<br><tt>};</tt>
<p><tt>struct B {</tt>
<br><tt>&nbsp;&nbsp; virtual void b1();</tt>
<br><tt>};</tt>
<p><tt>struct C: A, B {</tt>
<br><tt>&nbsp;&nbsp; virtual void b1();</tt>
<br><tt>} C_obj;</tt></ul>
The structure of <tt>C_obj</tt> is as follows (see <i><a href="http://cllweb.cup.hp.com/acxx/aCC/Documentation/objectlayout.html";>The
Layout of Objects in aCC</a></i> for details):
<ul><tt>&amp;C_obj + 0: pointer to virtual table for C and A-in-C</tt>
<br><tt>&amp;C_obj + 4: pointer to virtual table for B-in-C</tt></ul>
If you have a pointer <tt>pb</tt> to the <tt>B</tt> subobject of <tt>C_obj</tt>
(i.e., to <tt>&amp;C_obj + 4</tt>)and call <tt>pb->b1()</tt>, the call
must be dispatched to <tt>C::b1</tt> with <tt>this == pb - 4</tt>. This
parameter adjustment (subtraction of 4) is performed by the thunk.
<p>Note that each virtual table only contains plabels for functions declared
in the subclass associated with that virtual table. Virtual tables associated
with more than one class (because an object shares its virtual table pointer
with its primary base subobject) contains plabels for all the virtual member
functions declared in those classes (ordered from the deepest base class
to the complete class). Within a subclass, plabels are ordered in the order
the corresponding member functions were declared.
<br>&nbsp;
<h2>
<font face="Arial,Helvetica">Location of Bases</font></h2>

<p><br>The location of non-virtual base subobjects inside an enclosing
object can be computed from the static type of that enclosing object. I.e.,
the offset of the non-virtual subobject can be determined at compile-time.
However, for virtual bases this location is determined by the complete,
dynamic type of the object. Hence, there must be a mechanism to locate
at run-time where a particular virtual base subobject is with respect to
an enclosing object.
<p>This information is stored at negative offsets from the origin of the
virtual table if that virtual table is associated with a class that has
virtual bases. In that case, the entry at origin - 1 is zero. The entries
at origin - 2 to origin - M contain offsets (in bytes) to the virtual base
subobjects in a left-to-right, depth-first <i>postfix </i>traversal of
the hierarchy. Note that these offsets can be both negative and positive.
Virtual tables with entries at negative offsets of the origin and virtual
tables for virtual base subobjects are called VLD virtual tables, where
VLD stands for <i>virtual-base location dependent</i>.
<p>Each base (virtual or not) has one or more <i>base-ordinals</i> with
respect to an enclosing object: integers that identify a base subobject
within the designated enclosing object. The enclosing object itself is
numbered `0' (zero) and base classese are numbered using a left-to-right,
depth-first
<i>prefix
</i>traversal of the hierarchy. This traversal ignores
the fact that virtual base subobjects are shared and may therefore assign
more than one ordinal to a base-class: in those cases, the smallest assigned
ordinal is used. For example:
<ul><tt>struct P{ virtual void f(); };</tt>
<p><tt>struct Q: virtual P{};</tt>
<p><tt>struct R: virtual P{};</tt>
<p><tt>struct S: P, Q, virtual R{};</tt></ul>
An object of class S would be organized along the lines of:
<ul><tt>&nbsp;&nbsp;&nbsp;&nbsp; P</tt>
<br><tt>&nbsp;&nbsp; /v&nbsp; \v</tt>
<br><tt>P&nbsp; Q&nbsp;&nbsp; R</tt>
<br><tt>\&nbsp; |&nbsp; /v</tt>
<br><tt>&nbsp;&nbsp; S</tt></ul>
With respect to S, the non-virtual base P would be assigned ordinal `1',
the Q subobject would be `2', the virtual base subobject of type P would
get numbers `3' (traversing from Q) and `5' (traversing from R) and R would
be number `4'.
<p>The base-ordinal system is primarily used to encode unique names for
the various virtual tables produced by the compiler (i.e., mangling).
<h2>
<font face="Arial,Helvetica">Naming Virtual Tables</font></h2>

<p><br>The construction of a single object may need dozens of virtual tables
with contents that differ from one another. Hence all these tables need
to be accessible through unique names. Note that the name of a vtable is
always associated with its first entry, which is not always the same as
its <i>origin </i>(specifically, when the table contain virtual base offsets).
<p>A virtual table name is composed as follows:
<ul>`<tt>[Vtable</tt>' {`<tt>_</tt>' <i>mangled-type</i>} {`<tt>.</tt>'
<i>identity</i>}
{`<tt>.</tt>' <i>location</i>} `<tt>]key:</tt>' {<i>mangled-key-function</i>}</ul>
Items in braces are optional, but there is always exactly one of <i>mangled-type</i>
or <i>mangled-key-function</i>. Items in quotes are inserted literally
in the name, without surrounding whitespace.
<p>The <i>key-function</i> of a class-type is the first non-inline virtual
function declared in that type (i.e., not just inherited). If a class has
a key-function, its virtual tables will be emitted in the translation unit
that contains the definition of the key-function and the names of the virtual
tables will have the form:
<ul>`<tt>[Vtable</tt>' {`<tt>.</tt>' <i>identity</i>} {`<tt>.</tt>' <i>location</i>}
`<tt>]key:</tt>' {<i>mangled-key-function</i>}</ul>
Otherwise, if there is no key-function, the vtables are emitted in each
translation unit that needs to access that name (the CTTI linkage mechanism
eventually consolidates all those emitted copies into one). The name of
a vtable for a class without key-function (such as struct S above) has
the form
<ul>`<tt>[Vtable</tt>' {`<tt>_</tt>' <i>mangled-type</i>} {`<tt>.</tt>'
<i>identity</i>}
{`<tt>.</tt>' <i>location</i>} `<tt>]key:</tt>'</ul>
where <i>mangled-type</i> is the mangled name of the class described by
this table.
<p>Note that for virtual table pointers embedded inside virtual base subobjects
the <i>mangled-type</i> or <i>mangled-key-function</i> comes from the class
type that determines the structure of the object and not from the subobject
type itself.
<p>Once constructed, an object will have all its vtable pointers point
to vtables whose identity section has entries for the type of the complete
object. These virtual tables do not contain the {`<tt>.</tt>' <i>identity</i>}
component in their name. However, the C++ language definition requires
that during construction, the various subobjects exhibit the identity of
the base class under construction. For subobjects that are part of virtual
subobjects, this requires specialized <i>construction vtables</i>: these
encode the identity of a base-class, but the virtual base-layout within
the complete object. Construction vtables are always VLD vtables. The <i>identity</i>
field of a construction vtable is encoded as the base-ordinal of the subobject
being constructed with respect to the complete object. For example, while
the constructor of the class R above is building part of a complete object
of type S, the virtual base object of type P will have its first word point
to the construction vtable whose name is:
<ul><tt>[Vtable_1S.4.1]key:</tt></ul>
Indeed, S has no key-function (no virtual functions at all, in fact) and
the R subobject has base-ordinal `4' within S. The <i>location </i>field
in this name (`1' in this example) determines which subobject is pointing
to this vtable. This location is encoded as the base-ordinal with respect
to the subobject currently being constructed (the virtual subobject of
type P has ordinal `1' with respect to class R). Once the S object is fully
constructed, this pointer will point to the virtual table whose name is:
<ul><tt>[Vtable_1S.3]key:</tt></ul>
because the virtual base P subobject has ordinal `3' with respect to the
complete object of type S. The location field is omitted in the primary
vtable of a complete object. Hence, the first word of a completed S object
points to the table whose name is:
<ul><tt>[Vtable_1S]key:</tt></ul>
but the first word of the Q subobject during construction of a Q within
an S points to a table whose name is:
<ul><tt>[Vtable_1S.2.0]</tt></ul>
(the `0' cannot be omitted in table-names for incomplete objects since
we wouldn't be able to distinguish between the <i>identity </i>and <i>location
</i>fields.)
<br>&nbsp;
<h2>
<font face="Arial,Helvetica">VLDVTT: Virtual-base Location Dependent Virtual
Tables Table</font></h2>

<p><br>If a class requires construction vtables, pointers to all the vtables
required for the construction of a complete object of that class will be
grouped into a table called VLDVTT. The mangled name for such a VLDVTT
refers to its first entry and has the form:
<ul>`<tt>[VLDVTT</tt>' {`<tt>_</tt>' <i>mangled-type-2</i>}`]<tt>key:</tt>'
{<i>mangled-key-function</i>}</ul>
As with the naming of vtables, exactly one of <i>mangled-type-2</i> or
<i>mangled-key-function</i>
is used, depending on whether the complete derivation has a key-function
or not. The field <i>mangled-type-2</i> differs from the field <i>mangled-type</i>
in the naming of vtable names in that
<i>mangled-type-2</i> does not include
leading digits indicating the length of the typename. For example, the
VLDVTT for the class S above is mangled as:
<ul><tt>[VLDVTT_S]key:</tt></ul>
The table contains pointers to the origins of the various vtables used
for the corresponding complete type, in the lexicographical order of the
(implicit or explicit) pair {<i>identity</i>, <i>location</i>} E.g., the
contents of <tt>[VLDVTT_S]key:</tt> in HP-UX assembly notation are:
<ul><tt>[VLDVTT_S]key:</tt>
<br><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .ALIGN 8</tt>
<br><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .WORD [Vtable_1S]key:+12
; = 0xc</tt>
<br><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .WORD [Vtable_1S.3]key:</tt>
<br><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .WORD [Vtable_1S.4]key:+8
; = 0x8</tt>
<br><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .WORD [Vtable_1S.2.0]key:+12
; = 0xc</tt>
<br><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .WORD [Vtable_1S.2.1]key:</tt>
<br><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .WORD [Vtable_1S.4.0]key:+8
; = 0x8</tt>
<br><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .WORD [Vtable_1S.4.1]key:</tt></ul>

<h2>
<font face="Arial,Helvetica">Demonstration</font></h2>

<p><br>The program source pointed to by <a href="http://cllweb.cup.hp.com/acxx/aCC/Documentation/vtables1.txt";>this
link</a> should be compiled with `aCC -Wl,-E ...' to make its symbols loadable
at run-time. The program outputs an analysis of a complete object of type
S as described above.
</body>
</html>
#include <string.h>
#include <assert.h>
#include <dl.h>
#include <malloc.h>
#include <stdio.h>
#include <typeinfo>

typedef typedef void*(*shl_allocator)();

shl_symbol *sym_data, *sym_proc;
int n_data, n_proc;

static void
load_self_symbols() {
   shl_descriptor *d = 0;
   shl_gethandle(PROG_HANDLE, &d);
   printf("Executable file %s\n", d->filename);
   shl_t h = d->handle;
   n_data = shl_getsymbols(h, TYPE_DATA, EXPORT_SYMBOLS,
                           (shl_allocator)malloc, &sym_data);
   n_proc = shl_getsymbols(h, TYPE_PROCEDURE, EXPORT_SYMBOLS,
                           (shl_allocator)malloc, &sym_proc);
   printf("Loaded %d data symbols and %d procedure symbols.\n", n_data, n_proc);
}

static char const*
dataptr2sym(void const *a) {
   if (!a)
      return "<null pointer>";
   int offset = 0x3fff, closest = -1;
   for (int k = 0; k!=n_data; ++k) {
      int diff = (char const*)a-(char const*)sym_data[k].value;
      if (diff==0)
         return sym_data[k].name;
      else if (diff>0 && diff<offset) {
         closest = k;
         offset = diff;
      }
   }
   static char msg[80];
   if (closest==-1)
      sprintf(msg, "<address %x not found>", a);
   else
      sprintf(msg, "%s + %d", sym_data[closest].name, offset);
   return msg;
}

static char const*
procptr2sym(void const *p) {
   if (!p)
      return "<null pointer>";
   int offset = 0x3fff, closest = -1;
   // Strip L-bit from plabel p and find offset:
   char const* ep = *(char const**)(long(p)&(~2));
   for (int k = 0; k!=n_proc; ++k) {
      int diff = ep-*(char const**)(long(sym_proc[k].value)&(~2));
      if (diff==0)
         return sym_proc[k].name;
      else if (diff>0 && diff<offset) {
         closest = k;
         offset = diff;
      }
   }
   static char msg[80];
   if (closest==-1)
      sprintf(msg, "<address %x not found>", p);
   else
      sprintf(msg, "%s + %d", sym_proc[closest].name, offset);
   return msg;
}


int n_vbases(void const *vptr) {
   int offset = 0x3fff;
   for (int k = 0; k!=n_data; ++k) {
      int diff = (char const*)vptr-(char const*)sym_data[k].value;
      if (diff==0)
         return 0;
      else if (diff>0 && diff<offset) {
         offset = diff;
      }
   }
   return offset/4-1;
}


void describe_vtable(void const* vptr);

template<typename T>
void describe(T& obj) {
   printf("\n%s object: size==%d, address==%x\n",
          typeid(T).name(), sizeof(T), &obj);
   for (int offset = 0; offset<sizeof(T); offset+=sizeof(long)) {
      char const *p = (char const*)&obj+offset;
      if (*(long const*)p<='z')
         printf("object.%c\n", char(*(long const*)p));
      else
         printf("pointer %x to: %s\n", *(void**)p, dataptr2sym(*(void**)p));
   }
   printf("\n");
   for (int off = 0; off<sizeof(T); off+=sizeof(long)) {
      char const *p = (char const*)&obj+off;
      if (*(long const*)p>'z') {
         char const *sym = dataptr2sym(*(void**)p);
         if (strncmp(sym, "[Vtable", 7)==0) {
            describe_vtable(*(void const**)p);
         }
      }
   }
   printf("\n");
}

void describe_vtable(void const* vptr) {
   int n_vbs = n_vbases(vptr);
   int start = n_vbs? -(n_vbs+1): 0;
   int end = 4;
   while (*((void const**)vptr+end)!=0)
      ++end;
   printf("\nDumping %s from [%d] to [%d]\n", dataptr2sym(vptr), start, end);
   for(; start!=end+1; ++start) {
      char const *p = (char const*)vptr+start*sizeof(void*);
      if (*(long*)p>0x3ff) { // Does it look like a pointer?
         void* ptr = *(void**)p;
         if (long(ptr)&2) { // Does it look like a P-label with the L-bit set?
            printf("   +%3d: PLABEL for %s (%x)\n", start,
                   procptr2sym(ptr), *(void**)(long(ptr)&(~2)));
         } else
            printf("   +%3d: %s (%x)\n", start, dataptr2sym(ptr), ptr);
      } else
         printf("   +%3d: %d\n", start, *(long*)p);
   }
}

struct P { virtual void f(); };
struct Q: virtual P {};
struct R: virtual P {};
struct S: P, Q, virtual R {};

void P::f() {}

int main() {
   assert(sizeof(void*)==sizeof(long));
   load_self_symbols();
   S s;
   describe(s);
}