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