1. 程式人生 > >C++ : Object Model

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

by Stanley Lippman is the only book which hasdetailed descriptions on the underlying mechanism. Unfortunately thematerials in that book are mostly about some very old C++ compilers whichare rarely used today. But it is a great help for me to study the detailsabout g++.

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

object for thisclass. The virtual table slots can be accessed via positive, zero andnegative index through virtual pointers. The slots with non-negative indexare used to store addresses of virtual functions (or their thunks, seeCall virtual functions or of a g++ defined function __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.

  1. Layout the virtual pointer first if any.
  2. 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.

  1. The inversion of offset of the virtual pointer pointing to thissub-virtual-table is stored at index -2 from the virtual pointer.
  2. RTTI pointer is stored at index -1.
  3. 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.

The memory layout of class `A`

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.

  1. If all base classes have no virtual tables, the rule is the same asthe situation without inheritance (see last section);
  2. Otherwise, the first base class subobject with virtual pointers is put at thebeginning and other base class subobjects are put in their inheritance order;
  3. 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.

  1. If all base classes have no virtual tables and the derived class donot declare any virtual function, no virtual tables are created.
  2. 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).
  3. 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.
    1. copy the virtual table of the first base class which has one;
    2. 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);
    3. if a virtual function is declared (not overriding) in thederived class, append its address to the virtual table;
    4. update the RTTI pointer to points to the typeinfo objectfor class D.
  4. The second and subsequent sub-virtual-tables are created in theorder of the inheritance list, and the rules for creating them is asfollows.
    1. copy the virtual table of a corresponding base class B;
    2. 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);
    3. update the RTTI pointer to points to the typeinfo objectfor D;
    4. update the value at index -1, which should equal to theinversion of offset of the B subobject in D;
  5. After creating the virtual table of D update the virtualpointers in D 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.

The memory layouts of class A
The memory layouts of class B

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.

Memory layout of class A
Memory layout of class B
Memory layout of class C

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.

Member layouts of A
Member layouts of A
Member layouts of A

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.

General layout of a virtual table

A sub-virtual-table of a class D is composed of four or five parts:

  1. 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.
  2. 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.
  3. Inversion of offset of the virtual pointer pointing to this virtualtable, whose index is always -2. This is necessary fordynamic_cast<void*> in particular.
  4. RTTI pointer pointing to typeinfo object for D, whoseindex is always -1.
  5. 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.

  1. First layout the non-virtually inherited subobject as usual;
  2. Then layout the virtually inherited subobject. The rule is same.

And the rule for g++ to create virtual tables for a derived class Dis same as usual except

  1. The sub-virtual-tables from non-virtual base classes are createdfirst and then those from virtual base classes, corresponding to thelayout of the object.
  2. 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.
  3. Then the first sub-virtual-table is prepended with virtual base classoffset table.
  4. 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.

Member layout of class A
Member layout of class B
Member layout of class C

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:

  1. the entry of the called function must be found;
  2. 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 Bsubobject 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.

Memory layout of class A
Memory layout of class B
Memory layout of class C

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.

Memory layout of class C

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 Bsubobject 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 faddrrepresents 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 pmust 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 oftypepointer 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, grepand 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.