bug-gnu-utils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: multiple destructors - which gets linked in?


From: Michael Elizabeth Chastain
Subject: Re: multiple destructors - which gets linked in?
Date: Fri, 9 Jul 2004 15:38:58 -0400 (EDT)

Hi Andy,

A better mailling list for gcc questions is address@hidden .

But here's an answer.

gcc emits two or three versions of the object code for each
destructor.

They are:

  _ZN7DerivedD0Ev  Derived::~Derived [in-charge deleting]()
  _ZN7DerivedD1Ev  Derived::~Derived [in-charge]()
  _ZN7DerivedD2Ev  Derived::~Derived [not-in-charge]()

When the program destroys an object, the compiler generates code
to call one of the in-charge destructors for the object's complete
type.  The in-charge destructor calls not-in-charge destructors
for each of the non-virtual base classes, then calls not-in-charge
destructors for each virtual base.

Consider the classic diamond-shaped hierarchy:

  class A { };
  class B1 : virtual public A { ... };
  class B2 : virtual public A { ... };
  class C : virtual public B1, virtual public B2 { ... };

When the program destroys an object of type A:

  A::~A [in-charge]

When the program destroys an object of type B1:

  B1::~B1 [in-charge]
    A::~A [not-in-charge]

When the program destroys an object of type C:

  C::~C [in-charge]
    B2::B2 [not-in-charge]
    B1::B1 [not-in-charge]
    A::A [not-in-charge]

See, the destructor for the complete object is the only destructor
that can destroy the virtual base classes.  If you destroy an object
of type B1, then you need B1::~B1 [in-charge], which calls one of
the flavors of A::~A.  If you destroy an object of type C,
then C::~C [in-charge] calls B1::~B1 [not-in-charge], and
B1::~B1 [not-in-charge] does *not* call any of A::~A.  It's the
responsibility of the complete object type, and *only* the complete
object type, to construct/destroy virtual bases.

That's what the in-charge/not-in-charge distinction is all about.

I don't know what the "in-charge" versus "in-charge deleting"
distinction is about.

Here is more info on the C++ multi-vendor ABI standard.

  http://www.codesourcery.com/cxx-abi/
  http://www.codesourcery.com/cxx-abi/abi.html

This was originally developed for Itanium, but g++ uses it for
all C++ architectures.

> I need to know which instance of DerivedD2Ev gets linked into the final
> executable (don't ask why...), so I modified the assembler files and
> continued the build. However my modifications (inserted instructions)
> never make it into the final build.

_ZN7DerivedD2Ev will be called by the destructor of any class
which has 'Derived' directly as a base class, or directly or
indirectly as a virtual base class.

  class MoreDerived : public Derived         // will  call _ZN7DerivedD2Ev
  class MoreMoreDerived : public MoreDerived // won't call _ZN7DerivedD2Ev
  class V1 : public virtual Derived          // will  call _ZN7DerivedD2Ev
  class V2 : public V1                       // will  call _ZN7DerivedD2Ev

Again, each destructor calls the destructors for its immediate
non-virtual base classes.  And then the top-level destructor
calls the destructor for all the virtual base classes anywhere
in the inheritance hierarchy.

> Interestingly, there is no instance of it in Derived.S, just in
> ReallyDerived.S and in another file which references it.

Yup.  The compiler synthesizes these functions where they are used.
Derived::~Derived [not-in-charge]() is not used by Derived;
it's used by ReallyDerived.

If you have a lot of classes that have Derived as an immediate
base class, or directly or indirectly as a virtual base, then you'll
get a lot of Derived::~Derived [not-in-charge]() in a lot of
different .o files.  Depending on your platform, these might be
coalesced by the linker into a single function, or there might be
several copies in the object code.

That's what happens when you let the compiler synthesize methods.
It synthesizes them at point-of-use.

If you write class Derived like this:

  // Derived.h
  Derived::Derived
  {
    virtual ~Derived ();
  }

  // Derived.c
  Derived::~Derived () { ; }

Then there will be one copy of Derived::~Derived [not-in-charge],
and it will be in Derived.o.  That will make it easier for you
to mess with the assembly code.

Hope this helps,

Michael C




reply via email to

[Prev in Thread] Current Thread [Next in Thread]