This is the mail archive of the gcc-prs@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

c++/198: Re: Previously reported g++ bug (dated March 28th 2000)



>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:

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]