This is the mail archive of the
gcc-prs@gcc.gnu.org
mailing list for the GCC project.
c++/198: Re: Previously reported g++ bug (dated March 28th 2000)
- To: gcc-gnats at gcc dot gnu dot org
- Subject: c++/198: Re: Previously reported g++ bug (dated March 28th 2000)
- From: "Martin v. Loewis" <martin at loewis dot home dot cs dot tu-berlin dot de>
- Date: Tue, 9 May 2000 22:24:45 +0200
- References: <025b01bfb8da$720d7890$521bcac2@imsltd.com>
- Resent-Cc: gcc-prs at gcc dot gnu dot org, dmatthews at imsltd dot com
- Resent-Reply-To: gcc-gnats@gcc.gnu.org, "Martin v. Loewis" <martin@loewis.home.cs.tu-berlin.de>
>Number: 198
>Category: c++
>Synopsis: Extra dtor calls
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: unassigned
>State: analyzed
>Class: wrong-code
>Submitter-Id: net
>Arrival-Date: Tue May 09 13:36:00 PDT 2000
>Closed-Date:
>Last-Modified:
>Originator: dmatthews@imsltd.com (Dominic Matthews)
>Release: 2.95.2
>Organization:
>Environment:
>Description:
Original-Message-ID: <025b01bfb8da$720d7890$521bcac2@imsltd.com>
Date: Mon, 8 May 2000 11:43:59 +0100
BUG REPORT
Hi,
I believe we have run into a bug with the gcc compiler [version
2.95.2 19991024 (release) under RedHat Linux release 5.2, kernel
version 2.0.36 with binutils version 2.9.1.0.15] related to when
constructors/destructors are called. It seems that under certain
circumstances, an argument passed to a function does not have its
copy constructor called at function call time (allowed under ANSI)
but that its destuctor _does_ get called when the function returns,
leading to an imbalance of CTORs/DTORs. We came accross the problem
when attempting to build an application developed under Microsoft
VC++ 6 on WindowsNT. The application has been written as near as we
can be sure to be 100% ANSI complient and the Microsoft built version
works reliably under NT. In our application, we use reference
counting to keep memory usage to a minimum, and it is because of this
that we hit the problem I am now reporting - the use count of an
object hits zero, it's memory get freed, and the next access, that
ought to be legal, causes a segmentation fault because the object has
been deleted. We have put together an example that illustrates the
problem (attached). We have also been able to find a workaround that
proves the problem in one case (compiling the example with
-DWORKAROUND demonstrates this). We would be grateful if someone
could find the problem in the GCC compiler, and are willing to test
any patch produced. Note: We are not keen on using our 'workaround'
in the whole of our application since we do not know where else in
the code the bug might manifest itself. Thank You!
INSTRUCTIONS FOR BUILDING THE EXAMPLE
To build the example that shows-up the problem (too many DTORs getting
called), use command line:
g++ -O0 DtorBug.cpp
and run a.out;
To build the example that proves that the above illustrates a bug
(implements a workaround), use command line:
g++ -O0 DtorBug.cpp -DWORKAROUND
and run a.out;
RUNNING THE EXAMPLE
On running the example that shows up the problem, you will note that
the use count reported at the point of the last 'Hndl' copy CTOR is
2, but that 3 Hndl DTORs get called (the last one when the use-count
is already zero!).
On running the example with the 'work-around', you will note that the
last use-count reported is 3, and that 3 DTORs get called
correctly. The only difference between in the code is that the
function 'appendToMembers(..)' is called with a temporary object,
pre-initialized (see lines 270 to 277).
GCC INSTALL NOTES
I did not pass any options to 'configure' when installing gcc.
END.
>How-To-Repeat:
//***************************** GCC bug demo example to show up a CTOR/DTOR imbalance *****************************
//
// Type definitions and some (mostly!) inline code ...
#include <stdio.h>
#include <string.h>
//----------------------------
// Our general base type 'Any'
//----------------------------
class Any
{
public: // temporary, so my debug print can get to it...
mutable int nUseCount;
public:
virtual char* mytostring() { static char* name = "(no-name)"; return name; }
void incrementCounter() const {++nUseCount;}
int decrementCounter() const {return (--nUseCount) == 0;}
Any() : nUseCount(0) {}; // default ctor
Any(const Any &) : nUseCount(0) {}; // 'copy ctor' - really just clears usecount
virtual ~Any() {}; // dtor - virtual since all user classes will inherit from this
};
//------------------------------------------------------------------------
// Domain and Range types to represent the 2 halfs of the map-element type
//------------------------------------------------------------------------
class DomTypeBase : public Any
{
const char* str;
public:
DomTypeBase(const char* s) : str(s) {}
};
class DomType : public DomTypeBase
{
int valuePt2;
public:
char* mytostring() { static char* name0 = "DomType"; return name0; }
void testFunction() const
{
printf("\ntestFunction called");
fflush(NULL);
}
DomType(int val, const char* str) : DomTypeBase(str), valuePt2(val) {}
};
class RanType
{
int value;
public:
RanType(int val) : value(val) {}
};
//----------------------------------------------------------------
// Our reference-counted type 'Hndl' and its basetype 'BaseHandle'
//----------------------------------------------------------------
class BaseHandle
{
protected:
Any* Object;
//----------------------------------------
// Concrete protected member functionality
//----------------------------------------
// Incrememnt the sharing count
void AddLink()
{
if (Object != NULL)
Object->incrementCounter();
}
// Decrement the sharing count
void DelLink()
{
if (Object != NULL)
{
if (Object->decrementCounter())
{
delete Object;
}
}
}
// Re-assign the object pointer. We must allow for the possibility that
// the new object is the same as the old one. Either test for this
// condition, or increment the new object pointer's reference count
// before decrementing the old object's reference count (if we do it
// the other way round, we risk de-allocating the object if they are
// both the same).
void assign(const Any* newObject)
{
if (newObject != NULL)
{
newObject->incrementCounter();
}
DelLink();
Object = const_cast<Any*>(newObject);
}
// Copy assignment operator. This is used by the system-generated copy
// assignment operator of each derived class.
void operator=(const BaseHandle& x)
{
assign(x.Object);
}
BaseHandle() : Object(NULL) {}
// Copy ctor
BaseHandle(const BaseHandle &bh)
: Object(bh.Object) {};
// Construct from an object pointer
explicit BaseHandle(const Any* ob)
: Object(const_cast<Any*>(ob))
{
AddLink();
}
// Destructor
~BaseHandle()
{
DelLink();
Object = NULL;
}
};
class Hndl : public BaseHandle
{
public :
// Dereferencing operator, giving access to the object in a const way
const DomType& operator*() const
{
return *(operator->());
}
// Used for getting at the object to get at members in a 'const' way.
const DomType* operator->() const
{
return static_cast<const DomType*>(Object);
}
// Note: we allow the system to generate the copy constructor and the copy assignment operator
// Assign a handle from an object pointer
Hndl& operator=(const DomType* h)
{
assign(h);
return *this;
}
// Default constructor, creates a 'null handle'
Hndl() : BaseHandle()
{
}
// Build a handle from an object pointer
explicit Hndl(const DomType* xptr): BaseHandle(xptr)
{
}
// Hand added copy CTOR to allow us to see when a handle gets copied ..
Hndl(const Hndl & src) : BaseHandle(src.Object)
{
if(strcmp(src.Object->mytostring(), "DomType")==0)
{
printf("\n=====> Handle copy CTOR called to copy a '%s' (usecount = %d)!", src.Object->mytostring(), src.Object->nUseCount); fflush(NULL);
//__asm__
//( "int $3" );
}
}
~Hndl()
{
if(Object == NULL)
printf("\nARRGGHHH!! Trying to destroy NULL object!");
else if(Object->nUseCount == 0)
printf("\nERROR!!!! Too many destructors called!!!");
else
printf("\nDestroying handle object OK");
}
};
//----------------------------------
// Type to represent our map element
//----------------------------------
class MapElement
{
public:
Hndl dom; // reference-counted type
RanType ran;
MapElement(const Hndl d, const RanType r) : dom(d), ran(r) {}
};
//-----------------------------------
// Types to represent our stored node
//-----------------------------------
class Node : public Any
{
MapElement leaf;
public:
virtual MapElement operator [] (const int index) const
{
printf("\nIndexing operator (CONST) in 'Node' called!"); fflush(NULL);
return leaf;
}
virtual MapElement & operator [] (const int index)
{
printf("\nIndexing operator (NON-const) in 'Node' called!"); fflush(NULL);
return leaf;
}
explicit Node(const MapElement x) : leaf(x) {}
};
//----------------------------------------------------------------
// Our 'From' type - is reference-counted, but this isn't relevant
//----------------------------------------------------------------
class From : public BaseHandle
{
public:
const Node& operator * () const
{
return *(operator -> ());
}
const Node* operator -> () const
{
return static_cast<const Node*>(Object);
}
// CTOR - build a handle from an object pointer
explicit From(const Node* d) : BaseHandle(d) {}
};
//------------------------------------------------------------------------------------------------
// Type to act as the base for our test - just contains the function who's argument is in question
//------------------------------------------------------------------------------------------------
class Sequence
{
public:
void appendToMembers(Hndl newOne) const
{
newOne->testFunction(); // call a function on the reference-counted object ...
}
};
//---------------
// Our test class
//---------------
class Test
{
public:
void run()
{
const Sequence test = *(new Sequence());//test(Sequence());
int counter = 0;
From members( From( new Node( MapElement( Hndl( new DomType(0, "zero")), RanType(1) ) ) ) );
printf("\nNow comes the crunch..");
#if !defined (WORKAROUND)
test.appendToMembers( (*members)[counter].dom ); // <------------ THIS IS THE PROBLEMATIC LINE OF CODE
#else
// the fix..
Hndl tralala = (*members)[counter].dom;
printf("\nNow comes the crunch (2)..");
test.appendToMembers( tralala ); // Same call, but no operator calls here
#endif
printf("\nCrunch over!");
}
};
//**** The test itself ****
int main(int argc, char **argv)
{
Test ourTest;
ourTest.run();
printf("\n\n");
return 0;
}
// End.
>Fix:
>Release-Note:
>Audit-Trail:
>Unformatted: