[gnu.g++.bug] Destructor called too soon, Destructor not called - G++1.35

andrewt@watsnew.waterloo.edu (Andrew Thomas) (08/03/89)

System:	uVax II , Ultrix 2.0

G++ 1.35

Problem: The destructors for class instances passed as return values
(rather than returning a pointer or reference) get called too soon if
only a member of the class is of interest.  Also, sometimes no
destructor is called at all.

Program: The following program exhibits both behaviours described
above:

----------------- cut ----------------------------
#include	<stream.h>

class String
{
  char		*_ptr;
public:
  String (String&);
  String (char *);
  ~String();

  char*		ptr()			{ return (_ptr); }
};

String::String (String& old)
{
  char* a = new char[strlen(old._ptr) + 1];
  strcpy (a, old._ptr);
  _ptr = a;
  printf ("Constructing: %s (%x)\n", a, a);
}

String::String (char* old)
{
  char* a = new char[strlen(old) + 1];
  strcpy (a, old);
  _ptr = a;
  printf ("Constructing: %s (%x)\n", a, a);
}

String::~String()
{
  printf ("deleting %s (%x)\n", _ptr, _ptr);
  delete _ptr;
}

String my_func ()
{
  String	x = "Hello";
  return (x);
}

main ()
{
  printf ("Function: %s (%x)\n", my_func(), my_func());
  printf ("Function: %s (%x)\n", my_func().ptr(), my_func().ptr());
}

------------------------- cut -------------------------
The output of this program looks like:

Constructing: Hello (1c00)
Constructing: Hello (1c10)
deleting Hello (1c00)
Constructing: Hello (1c00)
Constructing: Hello (1c20)
deleting Hello (1c00)
Function: Hello (1c10)
Constructing: Hello (1c00)
Constructing: Hello (1c30)
deleting Hello (1c00)
deleting Hello (1c30)
Constructing: Hello (1c30)
Constructing: Hello (1c00)
deleting Hello (1c30)
deleting Hello (1c00)
Function: Hello (1c30)

Note only 6 destructors, and 8 constructors.  Also note that memory
address 1c30 is deleted with an automatically called destructor before
the String instance is actually used.
--

Andrew Thomas
andrewt@watsnew.waterloo.edu	Systems Design Eng.	University of Waterloo
"If a million people do a stupid thing, it's still a stupid thing." - Opus

gordon%stats.ucl.ac.uk@NSFNET-RELAY.AC.UK (Gordon Joly) (08/03/89)

Andrew,
Care to try this?
Gordon.

Surface mail: Dr. G.C.Joly, Department of Statistical Science,
      University College London, Gower Street, LONDON WC1E 6BT, U.K.
E-mail:                                            | Tel: +44 1 387 7050
 JANET (U.K. network) gordon@uk.ac.ucl.stats       |      extension 3636
 ARPA  gordon@stats.ucl.ac.uk[@nsfnet-relay.ac.uk] | FAX: +44 1 387 8057
 Relays: EAN: @ean-relay.ac.uk  UUCP: ...!uunet.uu.net!ucl-stats!gordon

-- < -- < -- < -- < -- < -- < -- < -- < -- < -- < -- < -- < -- < -- < -- 

# !/bin/sh
# This is a shell archive, shar, format file.
# To unarchive, feed this text into /bin/sh in the directory
# you wish the files to be in.

echo x - Makefile 1>&2
sed 's/^X//' > Makefile << 'End of Makefile'
X# to create & run "constructor/destructor" test for C++
X#
X# To run test, just utter "make" in this directory.
X#
X
X# filenames for errors
XEBC=errors-prob_bitwise_copying
XESEQ=errors-sequencing
X
XCC=g++				# use the GNU C++ compiler
X
Xtests:	testing_prog check.awk
X	testing_prog 2> $(EBC) > calling_sequence
X	if [ -s $(EBC) ]; then cat $(EBC); exit 1; else rm $(EBC); fi
X	# calling seq check is only valid if previous check was OK
X	awk -f check.awk calling_sequence > $(ESEQ)
X	if [ -s $(ESEQ) ]; then cat $(ESEQ); exit 1; else rm $(ESEQ); fi
X	# rm calling_sequence
X	echo " - Passed test OK"
X	
Xtesting_prog: x.h x.o testing_prog.o
X	$(CC) $(CFLAGS) -o testing_prog testing_prog.o x.o
X
Xtesting_prog.o: x.h testing_prog.cc
X	$(CC) $(CFLAGS) -c testing_prog.cc 2> /dev/null	# ignore warning msgs
X
Xx.o:	x.h x.cc
X	$(CC) $(CFLAGS) -c x.cc
X
Xclean:
X	rm -f *.o *..c core calling_sequence $(EBC) $(ESEQ)
X
Xclobber:	clean
X	rm -f testing_prog
End of Makefile
chmod 775 Makefile
echo x - README_local 1>&2
sed 's/^X//' > README_local << 'End of README_local'
XReceived: from cs.ucl.ac.uk by Stats.Ucl.AC.UK   via Satnet with SMTP
X           id aa02261; 29 Apr 88 16:39 BST
XTo: "Gordon Joly, Statistics, UCL" <gordon@uk.ac.ucl.stats>
Xcc: sam@uk.ac.ucl.cs
XSubject: Re: C++ test program 
XIn-reply-to: Your message of Thu, 28 Apr 88 16:45:44 +0100.
XDate: Fri, 29 Apr 88 16:36:24 +0100
XFrom: Paul Otto <otto@uk.ac.ucl.cs>
X
XThe main test I have is one which tests that constructors
X& destructors are correctly called.  If you have access, look
Xin /cs/research/2.5D/europa/otto/c++.bugs/ctor_dtor
Xat the READ_ME, Makefile & progs they refer to.  Should
Xbe reasonably painless to run the tests - but no C++ compiler
Xthat we've got passes them.  (Of course, the tests may be at
Xfault - but I don't think so - any checking is welcome.)
X
XIf you don't have access, let me know & I can mail them
X(or whatever).
X
XPaul
End of README_local
chmod 775 README_local
echo x - check.awk 1>&2
sed 's/^X//' > check.awk << 'End of check.awk'
X# Check output from ctor/dtor test prog.
X#
X# Input is assumed to be a sequence of lines of the form:
X#	<routine-name> called; "this" = <address>
X#	
X# An error is reported if, for any given address, the routine calls do not
X# occur as in the following BNF:
X#		{ ctor-call other-call* dtor-call }*
X# (Constructor call includes initializers.)
X#
X# NB This awk script is relatively fragile - it does not validate its
X# input, or do any other such checks for errors in the testing programs
X# themselves.
X#						GPO 16/11/87
X#	Minor mod (to tidy up prog slightly)	GPO 24/11/87
X
X	{	if (($1 == "x::x()") || ($1 == "x::x(x&)")){	# ctor
X			if (status[$5] == "defined"){
X				print "ERROR: multiple use of address " $5 " on line " NR
X			} else {
X				status[$5] = "defined"
X			}
X		} else if ($1 == "x::~x()"){			# dtor
X			if (status[$5] != "defined"){
X				print "ERROR: dtor called for unit'ed address " $5 " on line " NR
X			} else {
X				status[$5] = "undefined"
X			}
X		} else {					# other
X			if (status[$5] != "defined"){
X				print "ERROR: routine using unit'ed address " $5 " on line " NR
X			}
X		}
X	}
XEND	{	for (a in status){	# check that all dtors have been called
X			if (status[a] == "defined"){
X				print "ERROR: address " a " has not been dtor'ed by end of prog"
X			}
X		}
X	}
End of check.awk
chmod 775 check.awk
echo x - check.awk.bak 1>&2
sed 's/^X//' > check.awk.bak << 'End of check.awk.bak'
X# Check output from ctor/dtor test prog.
X#
X# Input is assumed to be a sequence of lines of the form:
X#	<routine-name> called; "this" = <address>
X#	
X# An error is reported if, for any given address, the routine calls do not
X# occur as in the following BNF:
X#		{ ctor-call other-call* dtor-call }*
X# (Constructor call includes initializers.)
X#
X# NB This awk script is relatively fragile - it does not validate its
X# input, or do any other such checks for errors in the testing programs
X# themselves.
X#						GPO 16/11/87
X
XBEGIN	{ nu = 0 }
X	{	if (($1 == "x::x()") || ($1 == "x::x(x&)")){	# ctor
X			if (status[$5] == "defined"){
X				print "ERROR: multiple use of address " $5 " on line " NR
X			} else {
X				status[$5] = "defined"
X			}
X		} else if ($1 == "x::~x()"){			# dtor
X			if (status[$5] != "defined"){
X				print "ERROR: dtor called for unit'ed address " $5 " on line " NR
X			} else {
X				status[$5] = "undefined"
X			}
X		} else {					# other
X			if (status[$5] != "defined"){
X				print "ERROR: routine using unit'ed address " $5 " on line " NR
X			}
X		}
X		used[nu++] = $5			# keep track of entries for end
X	}
XEND	{	for (i = 0; i < nu; i++){  # check that all dtors have been called
X			if (status[used[i]] == "defined"){
X				print "ERROR: address " used[i] " has not been dtor'ed by end of prog"
X				status[used[i]] = "reported"	# suppress further reports about this address
X			}
X		}
X	}
End of check.awk.bak
chmod 775 check.awk.bak
echo x - testing_prog.c 1>&2
sed 's/^X//' > testing_prog.c << 'End of testing_prog.c'
X#include "x.h"
X
X// first some routines to play with ...
X
Xx
Xf(x arg1, x arg2)
X{
X	x local1, local2, local3;
X
X	return local2;
X}
X
Xint
Xintf(x arg1, x arg2)
X{
X	x local1, local2, local3;
X
X	return 0;
X}
X
Xx
Xg()
X{
X	x local1, local2, local3;
X
X	return local2;
X}
X
Xx
Xh()
X{
X	x local1, local2, local3;
X
X	return local2;
X}
X
Xx
Xi(x arg)
X{
X	x local1, local2, local3;
X
X	return local2;
X}
X
X#ifndef pyr
Xx
Xdoubly_recursive(int depth_to_go)
X{
X	if (depth_to_go > 0){
X		return(f(doubly_recursive(depth_to_go-1),
X				doubly_recursive(depth_to_go-1)));
X	} else {
X		x local;
X		return(local);
X	}
X}
X#endif
X
X// Now call the routines & see what happens ...
Xint
Xmain()
X{
X	// test for calling of ctors & dtors on nested calls ...
X	{
X		x local;
X
X		local = f(g(),h());
X	}
X#ifndef pyr
X	// test for calling of ctors & dtors in recursive calls ...
X	doubly_recursive(3);
X#endif
X	// test for calling of ctors & dtors in nested calls occurring in
X	//  an expression
X	{
X		x a;
X		if (intf(i(g()),a))
X			7;
X	}
X	return 0;	// ought to return true status, but .....
X}
End of testing_prog.c
chmod 775 testing_prog.c
echo x - testing_prog.cc 1>&2
sed 's/^X//' > testing_prog.cc << 'End of testing_prog.cc'
X#include "x.h"
X
X// first some routines to play with ...
X
Xx
Xf(x arg1, x arg2)
X{
X	x local1, local2, local3;
X
X	return local2;
X}
X
Xint
Xintf(x arg1, x arg2)
X{
X	x local1, local2, local3;
X
X	return 0;
X}
X
Xx
Xg()
X{
X	x local1, local2, local3;
X
X	return local2;
X}
X
Xx
Xh()
X{
X	x local1, local2, local3;
X
X	return local2;
X}
X
Xx
Xi(x arg)
X{
X	x local1, local2, local3;
X
X	return local2;
X}
X
X#ifndef pyr
Xx
Xdoubly_recursive(int depth_to_go)
X{
X	if (depth_to_go > 0){
X		return(f(doubly_recursive(depth_to_go-1),
X				doubly_recursive(depth_to_go-1)));
X	} else {
X		x local;
X		return(local);
X	}
X}
X#endif
X
X// Now call the routines & see what happens ...
Xint
Xmain()
X{
X	// test for calling of ctors & dtors on nested calls ...
X	{
X		x local;
X
X		local = f(g(),h());
X	}
X#ifndef pyr
X	// test for calling of ctors & dtors in recursive calls ...
X	doubly_recursive(3);
X#endif
X	// test for calling of ctors & dtors in nested calls occurring in
X	//  an expression
X	{
X		x a;
X		if (intf(i(g()),a))
X			7;
X	}
X	return 0;	// ought to return true status, but .....
X}
End of testing_prog.cc
chmod 775 testing_prog.cc
echo x - x.cc 1>&2
sed 's/^X//' > x.cc << 'End of x.cc'
X// noddy class for testing purposes
X//
X// BEWARE: This file relies on being able to coerce a class pointer into
X// a long so that it can be printed.
X
X#include "x.h"
X#include <stream.h>
X
Xinline static void
Xtell_world(char * who, x* address)
X{
X	cout << who << " called; \"this\" = " << (long) address << "\n";
X	// Might be more convenient when debugging to use stderr, so that
X	//  stays in sync with error messages - but test makefile assumes
X	//  stdout.
X}
X
Xinline static void
Xerror_address_mismatch(char* routine, x* says, x* actual)
X{
X	cerr << "ERROR: address mismatch in call of " << routine
X		<< "; class instance thought address was " << (long) says
X		<< ", but it was actually " << (long) actual << "\n";
X}
X
Xx::x()
X{
X	tell_world("x::x()",this);
X	self = this;
X}
X
Xx::x(x& xref)
X{
X	tell_world("x::x(x&)",this);
X	self = this;
X	if (xref.self != &xref)
X		error_address_mismatch("x::x(x&)",xref.self,&xref);
X}
X
Xx&
Xx::operator=(x& rhs)
X{
X	tell_world("x::operator=(x&)",this);
X	if (rhs.self != &rhs)
X		error_address_mismatch("x::operator=(x&)",rhs.self,&rhs);
X	if (self != this)
X		error_address_mismatch("x::operator=(x&)",self,this);
X	return(*this);
X}
X
Xx::~x()
X{
X	tell_world("x::~x()",this);
X	if (self != this)
X		error_address_mismatch("x::~x()",self,this);
X}
End of x.cc
chmod 775 x.cc
echo x - x.h 1>&2
sed 's/^X//' > x.h << 'End of x.h'
X// simplest class which completely avoids bitwise copying.
X// good for testing calling of constructors and destructors.
X
Xclass x {
X	class x* self;	// Note own address for testing/debugging purposes
Xpublic:
X	x();
X	x(x&);
X	x& operator=(x&);
X	~x();
X};
End of x.h
chmod 775 x.h