C++ : Object Model
This text discusses the C++ object model implemented by g++, including thememory layout of C++ objects and implementation mechanism of polymorphism,virtual inheritance, pointer to data member and member function and RTTI.I have never seen any official document on these issues. As far as I knowInside the C++ object model
The mechanisms are studied by disassembling the object code generated byg++ so I can not guarantee it is correct. All examples are tested underg++ 4.4.3 and Ubuntu 10.04.
The memory layout of C++ objects
The memory model of an object is composed of two parts: the data layout andthe virtual table layout. The virtual table is used by g++ to implementpolymorphism and virtual inheritance and is accessed via the virtualpointer stored in the object.
A class has at least one virtual pointer if and only if it has virtual baseclasses or virtual functions. The virtual pointer points to the slotbeyond the RTTI pointer which points to the
typeinfo
__cxa_pure_virtual
, whichwill terminate the program, if a pure virtual function is declared. TheRTTI pointer is at index -1. The inversion of offset of the virtualpointer in this object is at index -2.
Without inheritance
When considering no inheritance the rule for g++ to layout an object is as follows.
- Layout the virtual pointer first if any.
- Layout non-static data members in their declaration order.
The storage of data members must satisfy the alignment requirement so theremay be padding between data members. But for simplicity this text do not takeit into consideration. It is irrelevant whether a data member is abuilt-in type or class type. So in examples
all data members are declaredas int
which occupy 4 bytes in my environment.
Static data members and non-virtual functions (static member functions andnon-static member functions) do not have any impact on the memory layout ofC++ objects and therefore ignored in this text.
Without inheritance, the rule for g++ to create virtual tables, if any, isas follows.
- The inversion of offset of the virtual pointer pointing to thissub-virtual-table is stored at index -2 from the virtual pointer.
- RTTI pointer is stored at index -1.
- Beginning from index 0 and so on are the virtual function slots,where the addresses of virtual functions or thunks are stored.
The following is a simple example.
struct A {
int a;
virtual void foo() { }
};
Its memory layout is shown as follows.
Add non-virtual inheritance
A derived object is created by concatenating all base class subobjects andappending data members declared by itself. The virtual pointers, if any,are updated appropriately.
After adding non-virtual inheritance the rule for g++ to layout anobject of a derived class
D
is as follows.
- If all base classes have no virtual tables, the rule is the same asthe situation without inheritance (see last section);
- Otherwise, the first base class subobject with virtual pointers is put at thebeginning and other base class subobjects are put in their inheritance order;
- The data members declared in
D
are put at the end.
Similarly, its virtual table is created by concatenating all base classvirtual tables (which is referred to as sub-virtual-table in this text)after appending the virtual functions declared in this class (not inheritedor overridden) to the first sub-virtual-table.
After adding non-virtual inheritance the rule for g++ to create virtualtables for a derived class
D
is as follows.
- If all base classes have no virtual tables and the derived class donot declare any virtual function, no virtual tables are created.
- Otherwise, if all base classes have no virtual tables and thederived class declares some virtual function, the rule for creating thevirtual table is the same as the situation without inheritance(see Without inheritance).
- Otherwise, if some base class has a virtual table, the virtual table of the derived class is created by concatenatingsub-virtual-tables. The rule to create the first sub-virtual-table isas follows.
- copy the virtual table of the first base class which has one;
- if a virtual function is overridden in the derived class,update its corresponding virtual table slot to the address of theoverriding function (which implements polymorphism);
- if a virtual function is declared (not overriding) in thederived class, append its address to the virtual table;
- update the RTTI pointer to points to the
typeinfo
objectfor classD
.
- The second and subsequent sub-virtual-tables are created in theorder of the inheritance list, and the rules for creating them is asfollows.
- copy the virtual table of a corresponding base class
B
; - if a virtual function in
B
is overridden, update itscorresponding virtual table slot to the address of thunk (seeCall virtual functions to the overriding function (which implementspolymorphism); - update the RTTI pointer to points to the
typeinfo
objectforD
; - update the value at index -1, which should equal to theinversion of offset of the
B
subobject inD
;
- copy the virtual table of a corresponding base class
- After creating the virtual table of
D
update the virtualpointers inD
object so that they correspond to the RTTI pointersone-by-one in increasing order of their addresses and each virtualpointer points to the slot beyond the RTTI pointer.
For multiple inheritance hierarchies these rules can be appliedrecursively.
According to above rules when considering no virtual inheritance the offsetof each data member is fixed no matter how many times the class isinherited, which means that the offset of a data member of a class relativeto the address of the class object or subobject of its some derived classis never changed.
Example 1: class B
inherits from
A
.
struct A {
int a;
virtual void foo() { }
};
struct B : A {
int b;
virtual void bar() { }
};
Their memory layouts are shown as follows.
Note that the offset of a
in class
A
is 4 and in the A
subobject ofclass
B
is also 4.
Example 2: multiple inheritance. Classes A
and
B
have their own virtualpointer and class
C
inherits from A
and
B
.
struct A {
int a;
virtual void foo() { }
};
struct B {
int b;
virtual void bar() { }
};
struct C : A, B {
int c;
virtual void fun() { }
};
Their layouts are shown as follows.
Note that the offset of class B
data member
b
in class
B
is 4 bytesand its offset in the class B
subobject of class
C
is also 4 bytes.
Example 3: multiple inheritance with the first base class lacking virtualpointer.
struct A {
int a;
};
struct B {
int b;
virtual void foo() { }
};
struct C: A, B {
int c;
virtual void bar() { }
};
Their layouts are shown as follows.
Add virtual inheritance
The virtually inherited base class subobject occurs only once in the mostderived class object, which results in that virtual base class subobjectdoes not has fixed offset in derived class object. So the virtual baseclass subobject must be accessed indirectly. g++ achieves this goal byputting virtual base class offset table, which contains offsets ofeach virtual base class subobject, in the virtual table. (“Virtualinheritance” of 3.4 of Inside the C++ Object Model)
In addition, as discussed in
Call virtual functions, the virtual function hasto adjust the
this
pointer sometimes, which is done by the thunk. Sothe thunk needs a way to calculate the adjustment value. For the situationwith non-virtual inheritance this can be done at compile time because theoffset of the subobject is fixed. But for the situation
with virtualinheritance this can only be done indirectly. Similarly g++ solves thisproblem by putting the adjustment value in the virtual table, one for eachvirtual function inherited from a virtual base class, for which g++ willgenerate a virtual thunk to
it. Note that only virtual thunk needs theadjustment value and thunk is never stored in the first sub-virtual-tablebecause the
this
pointer do not need to be adjusted when the virtualfunction is called through the first sub-virtual-table, so the firstsub-virtual-table does not have these values. For the second andsubsequent sub-virtual-tables
each virtual function inherited from avirtual base class has a corresponding adjustment value even if it is not athunk. If it is not a thunk the adjustment value is 0, which is used as aplaceholder, because it may be needed if the virtual function isoverridden
by some derived class and then a virtual thunk is generated.
Take all situations into consideration the general layout of asub-virtual-table is like the following.
A sub-virtual-table of a class D
is composed of four or five parts:
- Adjustment value table for virtual functions, one slot for eachvirtual function. The first sub-virtual-table and sub-virtual-tableswhich is not inherited virtually do not have this part.
- Virtual base class offset table, one slot for each virtual baseclass including all virtual base classes in the inheritance hierarchy.Its index begins from -3 and go on decreasing.
- Inversion of offset of the virtual pointer pointing to this virtualtable, whose index is always -2. This is necessary for
dynamic_cast<void*>
in particular. - RTTI pointer pointing to
typeinfo
object forD
, whoseindex is always -1. - Virtual function table which stores addresses of each virtualfunction, whose index begins from 0 and go on increasing. The virtualpointer is pointing to the first entry of this table.
Generally speaking the rule for g++ to layout an object is as follows.
- First layout the non-virtually inherited subobject as usual;
- Then layout the virtually inherited subobject. The rule is same.
And the rule for g++ to create virtual tables for a derived class
D
is same as usual except
- The sub-virtual-tables from non-virtual base classes are createdfirst and then those from virtual base classes, corresponding to thelayout of the object.
- The first sub-virtual-table is copied from the the firstnon-virtual base class which has a virtual table. If all non-virtualbase classes do not have a virtual table then the firstsub-virtual-table is created for class
D
and the rule is the sameas in Without inheritance. - Then the first sub-virtual-table is prepended with virtual base classoffset table.
- The second and subsequent sub-virtual-tables are prepended with aadjustment value table if its corresponding base class is virtuallyinherited and has declared some virtual functions and the values intheir virtual base class offset table are updated.
Let’s see an example.
struct A {
int a;
virtual void foo() { }
};
struct B {
int b;
virtual void bar() { }
virtual void fun() { }
};
struct C: A, virtual B {
int c;
void fun() { }
};
The layout of classes A
,
B
and C
is shown in Figures 11, 12 and 13 respectively.
In Figure 13 the sub-virtual-table _vptr_A
points to has noadjustment values because it is the first sub-virtual-table. The secondsub-virtual-table, which is pointed to by
_vptr_B
, has twoadjustment values because class
B
is virtually inherited and hasdeclared two virtual function. One is
B::bar()
at index 0 and itsadjustment value is 0 which is at index -3 and is used as a placeholder.The other is virtual thunk to
C::fun()
at index 1 and its adjustmentvalue is -12 which is at index -4. We can conclude that the more the indexof a virtual function slot is the less the index of its adjustment valueis. Putting it another way the index
of a virtual function slot and theindex of its corresponding adjustment value are symmetric.
How to access data members
Through objects
The compiler knows the offset of each data member in the object, even thedata member inherited from a virtual base class. And the dynamic type andthe static type of the object are the same so we can access the data memberdirectly. The following code
T obj;
obj.x = 1; // assign the data member x of class T
which access the data member x
of class
T
, is translated to thefollowing pseudocode
*(&obj + offset_of_x) = 1
Through pointers and references
Accessing through pointers to objects is the same as through references, sowe only discuss the situation of pointers.
The difference between accessing through pointers and through objects isthat the dynamic type and static type of the object which the pointer points to may bedifferent. The offset of data members, which is not inherited from a virtual baseclass, is never changed so we can access the data member directly just likethrough objects. Given the following inheritance hierarchy.
struct A {
int a;
};
struct B: A {
int b;
};
B b;
B *pb = &b;
A *pa = &b;
Note that pa
points to
A
subobject of B
.Then the accessing code is translated as follows.
pb->b = 1; // --> *(pb + offset_of_b_in_B) = 1
pb->a = 2; // --> *(pb + offset_of_a_in_B) = 2
pa->a = 1; // --> *(pa + offset_of_a_in_A) = 1
Here comes the difficulty when accessing a data member of a virtual baseclass through pointers and references. The difficulty lies in that theoffset the data member is not fixed any more. We can only access the datamember indirectly. First we must find the address of the virtual baseclass subobject, which can be done by accessing the virtual base classoffset table.
Given the following inheritance hierarchy.
struct A {
int a;
};
struct B: virtual A {
int b;
};
B b;
A *pa = &b;
B *pb = &b;
Then
pa->a = 1;
is translated to
*(pa + offset_of_a_in_A) = 1;
and
pb->a = 1;
is translated to
A *pa_t = pb + pb->_vptr_B[-3]; // find the address of class A subobject;
// its index is -3;
// _vptr_B is the virtual pointer of B
*(pa_t + offset_of_a_in_A) = 1; // the same as above
In summary the algorithm of accessing the data member througha pointer or reference is as follows.
If the data member is inherited from a virtual base class find the address of the subobject through virtual base class offset table and then access it through that address. Otherwise access the data member directly.
Through pointer to data member
According to above discussion we know we need two information to access adata member: the address of the object and the offset of the data member.In fact the pointer to data members (PMD) is just an offset. So we can accessthe data member through pointers to data members just like through pointersto objects.
But we must be careful with some special situations. For example,
struct A {
int a;
};
struct B {
int b;
};
struct C: A, B {
int c;
};
... &A::a ... // has type int A::*
... &C::a ... // has type int A::* because a is the member of class A
// (see clause 2 of 5.3.1 of C++ standard.)
When an assignment involves a pointer to member of a base class and apointer to member of a derived class the conversion is required because theoffset in the base class may be different from that in the derived class.In the following code
int C::*pmc = &C::b;
&C::b
has type
int A::*
, which is different from the type ofleft hand side (and have different offsets), so comes in the conversion:
int C::*pmb = &C::b + sizeof(A);
But a pointer to member of a virtual base class can not beconverted to a pointer to member of its derived class (See clause 2 of 4.11of “ISO C++98 Standard”). It is easy to agree with this when considering that thevirtual base class subobject do not have fixed offset.
How to call member functions
There are three kinds of member functions: static member functions,non-static member functions and virtual functions. Non-static and virtualMember functions are converted to an non-member function by adding anadditional parameter as the first parameter (a.k.a
the this
pointer,which is used to access the member of this object). The first parameterfor non-static and virtual functions must be a pointer to the class objector subobject in which the function is declared (not inherited
from a baseclass), which means the pointer must point to the subobject of a base classif the called function is from the base class.
Static member functions are not bound to an object and can’t accessnon-static members so they don’t require the
this
pointer. They arecalled just like global functions.
So when calling non-static and virtual member functions the following twotasks must be done correctly:
- the entry of the called function must be found;
- an appropriate
this
pointer must be passed as the firstparameter.
The first task is easy without polymorphism. The compiler knows theaddress of each function at compile time and knows which function iscalled. When involving polymorphism the compiler does not know whichfunction is actually called at compile time.
The second task means that if calling a function inherited from a baseclass the
this
pointer must be adjusted to point to the subobject ofthe base class which declares the function.
Call static member functions
Static member functions can be called through class name, objects, pointersand references. These different forms have the same semantic and thecompiler will generate codes that jump to the entry of the functiondirectly, which is the same as calling a global function.
Call non-static member functions
Non-static member functions can be called through objects, pointers andreferences. Calling non-static member functions is different from callingstatic member functions because it has to pass the
this
pointer asthe first parameter, which must points to the object or subobject of theclass which declares the function. That means the following code
obj.fun(); // obj is an object of class T
is translated to
_T_fun(&obj); // _T_fun is the mangling name of T::fun()
if fun()
is declared in class
T
, and translated to
B *p = &obj + offset_of_B_in_T; // adjust p to point to B subobject in T
_T_fun(p); // _T_fun is the mangling name of T::fun();
if fun()
is declared in some base class
B
of class
T
.
Calling through pointers and references is similar. When calling anon-static member function
fun()
through a pointer
T *p
, iffun()
is declared in class
T
the pointer p
is passed asthe first parameter, and if
fun()
is declared in some base class
B
of T
then the pointer
p
is adjusted to point to thesubobject of class
B
before passed as the first parameter.
The difference from calling through objects lies in the method of adjustingthe
this
pointer when the called function is declared in some virtualbase class, which is via the virtual base class offset table, because theoffset of virtual base class subobject is not fixed.
Call virtual functions
Calling virtual functions through objects is same as calling non-staticmember functions because it does not exhibit polymorphism.
The difficulty arises when calling through pointers and referencesbecause it must exhibit polymorphism.
The first is to find the actually called function. This can be solved viavirtual table, which can be found via the virtual pointer stored in eachobject. The address of each virtual function is stored in the virtualtable slots. Each virtual function is associated with an index of thetable, which is never changed in the inheritance hierarchy and is known atcompile time. First the pointer must be adjusted to point to the subobjectof the class in which the called virtual function is declared (see Callnon-static member functions for how to do the adjustment). Thenthe virtual pointer can be found through the adjusted pointer and theaddress of the virtual function can be found by indexing the virtual table.
The second task is passing an appropriate this
pointer, which theactually called virtual function expects to receive. The compiler does notknow which virtual function is called at compile time if it is invokedthrough
a pointer so it has no idea how to adjust the pointer beforepassing it as the first parameter. g++ solves this problem by storing apointer to a thunk to the virtual function if the adjustment is required orthe virtual function itself if not. A thunk is a snippet
of assembly codewhich can adjust the pointer correctly. Only when a virtual function froma base class is overridden is the adjustment required. When the adjustmentis required two situations must be considered. If the overridden virtualfunction comes from a
non-virtual base class the adjustment value can becalculated at compile time, but if it comes from a virtual base class theadjustment value can only be obtained by accessing the adjustment valuetable (see
vinheri). So g++ generates two kinds of thunks, one forthe non-virtual base class called non-virtual thunk and one for the virtualbase class called virtual thunk, respectively. In this arrangement foreach base class
B
of D
the entry point in the B-in-Dsub-virtual-table expects its
this
pointer to point the
B
subobject of D
. The thunk can do the adjustment if required.
The following example can show when the adjustment is required.
stuct A {
int a;
virtual void foo() {
cout << "A::foo()\n";
}
};
struct B {
int b;
virtual void bar() {
cout << "B::bar()\n";
}
};
struct C: A, B {
int c;
void foo() {
cout << "C::foo()\n";
}
void bar() {
cout << "C::bar()\n";
}
}
The memory layout corresponding to the above code is shown as below.
When calling C::foo()
through a pointer to
A
the adjustment isnot required, because the
A
subobject in
C
has zero offset, thevalue of the pointer is already right (C::foo()
receives a pointer toC
as the first parameter because it is declared in
C
). Butwhen calling
C::fun()
through a pointer to B
the adjustment isrequired, because the
B
subobject in
C
has non-zero offset (8bytes in this example), the pointer must be subtracted by 8 before passingto
C::fun
(which receives a pointer to
C
as the firstparameter) as the first parameter. So the compiler generates a non-virtualthunk (class
B
is non-virtually inherited) to do the adjustment, asshown in the figure where a pointer to the thunk is stored in the virtualtable. Because class
B
is non-virtually inherited the compiler knowsthe adjustment offset value (8 bytes in the above example) at compile time.So the second sub-virtual-table (which comes from
B
) does not have aadjustment value table. But when involving virtual inheritance, due to thefact that the offset is not fixed any more, the adjustment must be doneindirectly and a adjustment value table is created, as
shown in thefollowing example.
If all is the same as above except that B
is virtually inherited thenC
will have the layout in Figure 17.
Note that B
subobject is put at the end because it is virtuallyinherited. As shown in the figure the adjustment value is stored in thevirtual table with index -12 (and the value is -12 because
B
subobject has an offset of 12), which is used by the virtual thunk toadjust the pointer.
In summary, if class B
is some base class of
D
and fun()
is a virtual function declared in
B
with index
idx
in thevirtual table, then the following code
pb->fun(); // pb is a pointer to B
is translated to
pb += offset(_vptr); // Find the _vptr which can address the virtual table
// which fun resides in.
_vptr = *pb; // Get the virtual pointer.
(*_vptr[idx])(pb); // Find the entry to fun() and call it.
Pointer to member function
When taking the address of a non-static member function of a class by classname the type of the result is “pointer to member function” (PMF) (Seesection 5.3.1 clause 2 of [C++98]). It is incomplete because it can not beused to call a function by itself unless bound to the address of a classobject.
Remember that two kinds of information are required to call a memberfunction: the entry point to the function and the
this
pointer. Fornon-virtual functions the entry point can be obtained at compile time andthe
this
pointer is the address of the object or subobject in whichthe function is declared, so an offset must be provided to get thisaddress. For virtual functions the entry point can only be obtained viathe virtual table.
A class may have multiple virtual tables (and thusmultiple virtual pointers), so a offset must be provided to get the virtualpointer which can access the virtual table and a index into the virtualtable must also be provided to get the entry point. The
this
pointeris just the address of the virtual pointer, which equals to the address ofthe subobject in which the function is declared.
According to above discussion, both situations need an offset to calculatethe address of the subobject in which the function is declared. For thenon-virtual function the address of the function is required and for thevirtual function an index to the virtual table is required. In fact PMF isa structure declared as
struct PMF {
union {
UINT32 faddr; // entry point to non-virtual function
UINT32 index; // index to the virtual table plus 1
};
UINT32 offset; // offset of the subobject
};
The member faddr
of PMF is the address of the function if PMF pointsto a non-virtual function. The member
index
is the index of thisfunction in the virtual table plus 1 if PMF points to a virtual function.In both cases the member
offset
is the offset of the subobject of theclass in which this function is declared. Curiously! Why plus 1? Becausewe have to distinguish whether a PMF pointer is pointing to a virtualfunction or not. Calling virtual
and non-virtual functions is verydifferent. faddr
and index of virtual functions are always even, soafter adding 1 to the index field of PMF we can now decide if a PMF pointeris pointing to a virtual function or not by
checking if the first field ofPMF is even or not. The real index can be obtained by subtracting 1 fromit. The second reason why plus 1 is that we must distinguish whether a PMFpointer is a null pointer or not. Zero value of
index
or faddr
represents a null PMF pointer independent of the offset value.
If pmf
is a PMF pointer of type
void (T::*)()
andp
is a pointer to class
T
, then the following code
(p->*pmf)();
is translated to
if (pmf.index & 1 != 0) {
// virtual function invocation
p += pmf.offset; // find the vptr
vptr = *p;
(*vptr[pmf.index-1]))(p); // call the virtual function
} else {
// non-vritual function invocation
(*pmf.faddr)(p + pmf.offset);
}
If pmf
is a PMF pointer of class
B
and p
is a pointer toa class
D
derived from B
then calling
pmf
through p
must be done after
p
is adjusted to point to B
subobject.
Like pointer to data member, a pointer of type
pointer to member of `B`,where `B` is a class type, can be converted to a pointer oftype
pointer to member of
D
, where D
is a derived class ofB
. The member
faddr
or index
does not have to adjustedbecause it never changes. Only the member
offset
needs to beadjusted just like the situation of PMD.
As same as PMD, a pointer to member function of a virtual base class cannot be converted to a pointer to member function of its derived class (Seeclause 2 of 4.11 of [c++98]). The reason is same.
Type identification
According to the standard (see clause 3 of 5.2.8 of [c++98]) when theoperand of
typeid
is not polymorphic class type, the result refers toa
typeinfo
object representing the static type of the operand. Inthis situation the address of the
typeinfo
object can be calculatedat compile time.
When the operand of typeid
is a polymorphic class type the resultrefers to a
typeinfo
object representing the dynamic type of theoperand. In this situation the address of the
typeinfo
object cannot be calculated at compile time, and the RTTI pointer stored in thevirtual table is just for addressing this problem.
In summary the following code
typeid(exp);
is translated to
T obj = exp;
obj._vptr[-1]; // access the RTTI pointer
if the type of exp
is a polymorphic class type
T
, or return theaddress of the
typeinfo
object if the type of exp
is notpolymorphic.
Note that even if the result of an expression is a class type which has aRTTI pointer but is not polymorphic (the RTTI pointer is obtained throughvirtual inheritance)
typeid
applied on it still gets a result refersto a
typeinfo
object representing the static type, not the dynamictype, of the operand.
Dynamic cast
Until now I only learned that the dynamic cast expression
dynamic_cast<T>(v)
is rewritten by g++ as an invocation of a function__dynamic_cast(v, p1, p2, value)
, where
p1
is a pointer tothe
typeinfo
object representing the static type of
v
and p2
is a pointer to the
typeinfo
object representing the typepointed to by
T
. The meaning of value
is unknown. It seemslike an integer value.
Addition: The code for printing the virtual table
The following is the code for printing the virtual table of a class, whichdepends on the Linux tools
objdump
, AWK
,
cut
, grep
and
c++filt
.
/*
* print_vtable --
* Print the virtual table of a class given its name.
*/
void print_vtable(const char *name)
{
char s[100];
/*
* Get the beginning and end addresses of the virtual table
* by parsing the disassembled code. The label for a virtual
* table of a class whose name is "xyz" is "_ZTV3xyz" where 3
* is the length of the class name.
*/
FILE *fp;
unsigned int *vp, *vpe;
snprintf(s, sizeof(s),
"objdump -D ./a.out | "
"awk '/<_ZTV%d%s>: *$/{print $1; f=1; next}"
"/^0[0-9]+/ && f==1 {print $1; exit}'",
strlen(name), name);
fp = popen(s, "r");
if (fgets(s, sizeof(s), fp) == NULL)
return;
vp = (unsigned int *) strtol(s, NULL, 16);
fgets(s, sizeof(s), fp);
vpe = (unsigned int *) strtol(s, NULL, 16);
pclose(fp);
/*
* Print the virtual table and the virtual function name
* which is obtained by using c++filt to decode the mangled
* name in the object code.
*/
printf("vtable for %s:\n", name);
printf("-------------------\n");
for (; vp < vpe; vp++) {
UINT32 t = *vp;
if (t != 0 && ((t<<1)>>1) == t) {
printf("%#10x ", t);
snprintf(s, sizeof(s),
"objdump -D ./a.out | grep \'^%08x\' |"
"cut -d'<' -f2 | cut -d'>' -f1 | c++filt", *vp);
fp = popen(s, "r");
if (fgets(s, sizeof(s), fp) != NULL)
fputs(s, stdout);
else
printf("\n");
pclose(fp);
} else
printf("%10d\n", t);
}
printf("\n");
}
Note
After I have completed this text I encountered a standard effort to specifythe layout of C++ objects among other things, that is Itantium C++ ABI.That document is more detailed than this one and has more authority.
References
- [Lippman] Stanley Lippman, Inside the C++ object model, AddisonWesley, 1996.
- [C++98] ISO C++ Standard, 1998 edition.
- [c++filt] c++filt(1), a g++ name demangling tool.