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." - Opusgordon%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