[Gc] for C++ GC

Ben Hutchings ben.hutchings at businesswebsoftware.com
Wed Aug 18 16:04:01 PDT 2004


Hans Boehm <hans.boehm at hp.com> wrote:
> Ben Hutchings <ben.hutchings at businesswebsoftware.com> wrote:
> > Wei Bing <bwei at dyn.com> wrote:
> > > I know the following fact:
> > > class test: gc_cleanup 
> > >  { 
> > >  Public: 
> > >           ~test() 
> > >           { 
> > >            ... 
> > >           } 
> > >  };
> > > new test[10];
> > > But the destructor is only called once not 10 times during
> > > garbage collection.
> > > Is that correct? 
> >
> > No, of course it's not correct! It's a bug which should be fixed.
> > If I understand things correctly, the GC only supports one
> > finaliser per allocation but gc_cleanup::gc_cleanup tries to
> > register a separate finaliser for each object and this fails for
> > all but the first in an array. Either array-new should be disabled
> > in gc_cleanup or gc_cleanup should somehow chain finalisers for
> > the elements of an array.
>
> It's a misfeature, but one that's been documented in gc_cpp.h forever.
> (See point 3 under cautions for a suggested workaround.) 

Ah, yes, so it has.

> I don't see how to fix it, given the current C++ spec.  Operator new[]
> doesn't seem to get enough information to determine the number of
> array elements, their locations, and  the element destructor.  If
> anyone has an idea ...

It seems to me that it would be better to disable it by declaring it
private in gc_cleanup than to leave it broken.

I think I see a way to make it work, though:

class gc_cleanup : virtual public gc
{
public:
    inline gc_cleanup();
    inline virtual ~gc_cleanup();
private:
    inline static void GC_cdecl cleanup(void * GC_obj, void * GC_data);
    ptrdiff_t GC_next_displ;
};

inline void gc_cleanup::cleanup(void * GC_mem_base, void * GC_data)
{
    ptrdiff_t GC_displ = (ptrdiff_t)GC_data;
    ptrdiff_t GC_next_displ;
    do
    {
        gc_cleanup * GC_obj =
            (gc_cleanup *)((char*)GC_mem_base + GC_displ);
        GC_next_displ = GC_obj->GC_next_displ;
        GC_obj->~gc_cleanup();
    }
    while ((GC_displ = GC_next_displ) != 0);
}

inline gc_cleanup::gc_cleanup()
    : GC_next_displ(0)
{
    void * GC_mem_base = GC_base(this);

    if (GC_mem_base)
    {
        ptrdiff_t GC_displ = (char *)this - (char *)GC_mem_base;
        GC_finalization_proc GC_old_proc;
        void * GC_old_data;

        // Don't call the debug version, since this is a real base
        // address.
        GC_register_finalizer_ignore_self( 
            GC_mem_base, &cleanup, (void *)GC_displ,
            &GC_old_proc, &GC_old_data);

        if (GC_old_proc)
        {
            if (GC_old_proc == &cleanup)
            {
                // Link to the previous finalizable object in this
                // block.
                GC_next_displ = (ptrdiff_t)GC_old_data;
            }
            else
            {
                // Restore existing finalizer.
                GC_register_finalizer_ignore_self(
                    GC_mem_base, GC_old_proc, GC_old_data, 0, 0);
            }
        }
    }
}

This is just off the top of my head, totally untested, no express or
implied warranty etc.

Ben.


More information about the Gc mailing list