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

tiemann@SUN.COM (Michael Tiemann) (09/04/89)

This is a great example of C++ abuse.  It shows how different C and
C++ can be.  My comments appear as //** (apologies to anyone without
the GNU C preprocessor :-)

Please do not take my comments as being derogatory towards Andrew or
his code.  I am just using this code because it serves as such a good
example.  When I refer to `you', I refer to the general second person
of the C++ programming community.

   Date: 3 Aug 89 04:11:06 GMT
   From: watmath!watcgl!andrewt@uunet.uu.net  (Andrew Thomas)
   Organization: University of Waterloo, Waterloo, Ontario, Canada
   Sender: bug-g++-request@prep.ai.mit.edu

   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 ----------------------------
//** Inclusion of <stream.h> probably for luck--see below.
   #include	<stream.h>

//** A perfectly good C++ class
   class String
   {
     char		*_ptr;
   public:
     String (String&);
     String (char *);
     ~String();

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

//** A perfectly good C++ copy-constructor
   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);
   }

//** A perfectly good C++ initialization-constructor
   String::String (char* old)
   {
     char* a = new char[strlen(old) + 1];
     strcpy (a, old);
     _ptr = a;
     printf ("Constructing: %s (%x)\n", a, a);
   }

//** A perfectly good C++ destructor
   String::~String()
   {
     printf ("deleting %s (%x)\n", _ptr, _ptr);
     delete _ptr;
   }

//** A perfectly good C++ value-producing function
   String my_func ()
   {
     String	x = "Hello";
     return (x);
   }

//** Now here's where all hell breaks loose...
   main ()
   {
//** Here printf is used, in spite of the fact that
//** <stream.h> was #included.  If this went through
//** the stream interface, attempts to `print' a String
//** would be caught as compile-time errors.  Instead,
//** the go into the `...' black-hole of printf...
     printf ("Function: %s (%x)\n", my_func(), my_func());

//** Here we get lucky: the return type of String::ptr () is
//** `char *', which is a builtin C type, so the call to
//** printf succeeds.
     printf ("Function: %s (%x)\n", my_func().ptr(), my_func().ptr());
   }

   ------------------------- cut -------------------------
   The output of this program looks like:
//** Note!! This is on a VAX.  On a Sun4, this program core-dumps.
//** Explanation below.

   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.
//** You have to look at the assembly code to actually make that
//** statement.  In the code I looked at, everything is as it should
//** be.  It's been a year since I said this, but it's still true: the
//** most difficult way to observe code is from the behavior of a
//** program, because under those conditions, you must make
//** assumptions and interpretations.  Slightly easier, though still
//** difficult and subject to assumptions it to look at output from a
//** C++ to C translator, such as cfront.  For a really good
//** understanding of what is going on, the assembly code is really
//** where its at.
   --

   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

Ok.  Here is why the program will never work under GNU C++.  If you
have a function which takes an aggregate parameter that has a
destructor, there are two places where the object could be destroyed.
One place is in the scope of the caller, the other in the scope of the
callee.

In the case of the former, the aggregate parameter cannot be passed in
the stack.  Instead, a local temporary must be allocated, and a
reference to that temporary passed to the callee.  If space in the
stack is used to hold the object, it could get clobbered when the
callee returns, since the stack space could be deallocated by the
return statement, and the caller has no control over that.  Therefore,
if there are N calls to this function, then there must be N places
where cleanup code is emitted.

In the case of the latter, one only needs to have the cleanup code
extant in one location: the called function.  The callee's stack frame
remains allocated until the callee returns, so arguments can be passed
*in* the stack, rather than just passing references to the caller's
frame.  This is more efficient in both time (one fewer indirection),
and space (only one site for cleanup code).  And it compiles faster.

Cfront implements the first mechanism, while GNU C++ implements the
second.  The 2.0 reference manual states that either is an acceptable
way of doing business.  HOWEVER, if you do it the GNU C++ way, you
must be honest!  Namely, if you pass an object (not a reference to an
object) to a function, then such passing must be done type-correctly.
You can pass it via varargs, but that function must be prepared to
call a destructor by hand, just as it pulls arguments from the
argument list by hand.  When you pass objects in the stack to printf,
then tell printf `print a %s', printf may expect that the argument
came in a register (such as on my Sun4), when in fact it came on the
stack (such as a String object must).  You won't necessarily notice a
problem on a machine that passes both built-in types and objects on
the stack (such as a VAX), but that does not mean a problem does not
exist.  The reason that two destructors were missed is because
`printf' was responsible for calling them, and didn't.

Hope this was useful...

Michael

cbcscmrs@csun.edu (09/05/89)

What Andrew needs to learn about, are ``conversion operators''
(a term used by Bjarne Stroustrup.) (If he already knows about
them, he should learn to use them.)

The type of the argument to a printf %s should be a char*,
not an argument of type String.  Therefore one should cast
the String to a char * as in:

       printf ("Function: %s (%x)\n", (char*)my_func(),
	       (char*)my_func());

That's about it, oh, just one other thing, in class String,
one does need to tell c++ _how_ to convert the thing into
a char* type object.

class String {
...
     operator char* () {
	 return _ptr;
     }
...
}

Now your set to go, just have a prototype for the functions
that you call, or barring that (or in the case of ``...'')
just type cast them into the right types.

With these changes, his program works just fine.


Here is the article, just in case you missed it:

In article <8909040747.AA00164@teacake.sun.com> tiemann@lurch.stanford.edu writes:
!This is a great example of C++ abuse.  It shows how different C and
!C++ can be.  My comments appear as //** (apologies to anyone without
!the GNU C preprocessor :-)
!
!Please do not take my comments as being derogatory towards Andrew or
!his code.  I am just using this code because it serves as such a good
!example.  When I refer to `you', I refer to the general second person
!of the C++ programming community.
!
!   Date: 3 Aug 89 04:11:06 GMT
!   From: watmath!watcgl!andrewt@uunet.uu.net  (Andrew Thomas)
!   Organization: University of Waterloo, Waterloo, Ontario, Canada
!   Sender: bug-g++-request@prep.ai.mit.edu
!
!   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 ----------------------------
!//** Inclusion of <stream.h> probably for luck--see below.
!   #include	<stream.h>
!
!//** A perfectly good C++ class
!   class String
!   {
!     char		*_ptr;
!   public:
!     String (String&);
!     String (char *);
!     ~String();
!
!     char*		ptr()			{ return (_ptr); }
!   };
!
!//** A perfectly good C++ copy-constructor
!   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);
!   }
!
!//** A perfectly good C++ initialization-constructor
!   String::String (char* old)
!   {
!     char* a = new char[strlen(old) + 1];
!     strcpy (a, old);
!     _ptr = a;
!     printf ("Constructing: %s (%x)\n", a, a);
!   }
!
!//** A perfectly good C++ destructor
!   String::~String()
!   {
!     printf ("deleting %s (%x)\n", _ptr, _ptr);
!     delete _ptr;
!   }
!
!//** A perfectly good C++ value-producing function
!   String my_func ()
!   {
!     String	x = "Hello";
!     return (x);
!   }
!
!//** Now here's where all hell breaks loose...
!   main ()
!   {
!//** Here printf is used, in spite of the fact that
!//** <stream.h> was #included.  If this went through
!//** the stream interface, attempts to `print' a String
!//** would be caught as compile-time errors.  Instead,
!//** the go into the `...' black-hole of printf...
!     printf ("Function: %s (%x)\n", my_func(), my_func());
!
!//** Here we get lucky: the return type of String::ptr () is
!//** `char *', which is a builtin C type, so the call to
!//** printf succeeds.
!     printf ("Function: %s (%x)\n", my_func().ptr(), my_func().ptr());
!   }
!
!   ------------------------- cut -------------------------
!   The output of this program looks like:
!//** Note!! This is on a VAX.  On a Sun4, this program core-dumps.
!//** Explanation below.
!
!   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.
!//** You have to look at the assembly code to actually make that
!//** statement.  In the code I looked at, everything is as it should
!//** be.  It's been a year since I said this, but it's still true: the
!//** most difficult way to observe code is from the behavior of a
!//** program, because under those conditions, you must make
!//** assumptions and interpretations.  Slightly easier, though still
!//** difficult and subject to assumptions it to look at output from a
!//** C++ to C translator, such as cfront.  For a really good
!//** understanding of what is going on, the assembly code is really
!//** where its at.
!   --
!
!   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
!
!Ok.  Here is why the program will never work under GNU C++.  If you
!have a function which takes an aggregate parameter that has a
!destructor, there are two places where the object could be destroyed.
!One place is in the scope of the caller, the other in the scope of the
!callee.
!
!In the case of the former, the aggregate parameter cannot be passed in
!the stack.  Instead, a local temporary must be allocated, and a
!reference to that temporary passed to the callee.  If space in the
!stack is used to hold the object, it could get clobbered when the
!callee returns, since the stack space could be deallocated by the
!return statement, and the caller has no control over that.  Therefore,
!if there are N calls to this function, then there must be N places
!where cleanup code is emitted.
!
!In the case of the latter, one only needs to have the cleanup code
!extant in one location: the called function.  The callee's stack frame
!remains allocated until the callee returns, so arguments can be passed
!*in* the stack, rather than just passing references to the caller's
!frame.  This is more efficient in both time (one fewer indirection),
!and space (only one site for cleanup code).  And it compiles faster.
!
!Cfront implements the first mechanism, while GNU C++ implements the
!second.  The 2.0 reference manual states that either is an acceptable
!way of doing business.  HOWEVER, if you do it the GNU C++ way, you
!must be honest!  Namely, if you pass an object (not a reference to an
!object) to a function, then such passing must be done type-correctly.
!You can pass it via varargs, but that function must be prepared to
!call a destructor by hand, just as it pulls arguments from the
!argument list by hand.  When you pass objects in the stack to printf,
!then tell printf `print a %s', printf may expect that the argument
!came in a register (such as on my Sun4), when in fact it came on the
!stack (such as a String object must).  You won't necessarily notice a
!problem on a machine that passes both built-in types and objects on
!the stack (such as a VAX), but that does not mean a problem does not
!exist.  The reason that two destructors were missed is because
!`printf' was responsible for calling them, and didn't.
!
!Hope this was useful...
!
!Michael

pcg@thor.cs.aber.ac.uk (Piercarlo Grandi) (09/06/89)

In article <8909040747.AA00164@teacake.sun.com> tiemann@SUN.COM (Michael Tiemann) writes:

   [ ... on passing objects with a destructor to a varargs function ... ]

   Cfront implements the first mechanism, while GNU C++ implements the
   second.  The 2.0 reference manual states that either is an acceptable
   way of doing business.  HOWEVER, if you do it the GNU C++ way, you
   must be honest!  Namely, if you pass an object (not a reference to an
   object) to a function, then such passing must be done type-correctly.
   You can pass it via varargs, but that function must be prepared to
   call a destructor by hand, just as it pulls arguments from the
   argument list by hand.

Actually, in 2.0 you are simply forbidden from passing objects
with a *constructor* (or the assignment operator) to varargs
functions. The problem above cannot arise.  (see section 5.11,
page 229, Lippman's). Obviously the rationale is the same as that
given by Tiemann.
--
Piercarlo "Peter" Grandi           | ARPA: pcg%cs.aber.ac.uk@nsfnet-relay.ac.uk
Dept of CS, UCW Aberystwyth        | UUCP: ...!mcvax!ukc!aber-cs!pcg
Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@cs.aber.ac.uk

nagle@well.UUCP (John Nagle) (09/08/89)

In article <2381@csun.edu-> cbcscmrs@ma.csun.edu (Mike Stump) writes:
->[Insult deleted]
->The type of the argument to a printf %s should be a char*,
->not an argument of type String.  Therefore one should cast
->the String to a char * as in:
->
->       printf ("Function: %s (%x)\n", (char*)my_func(),
->	       (char*)my_func());
->
->That's about it, oh, just one other thing, in class String,
->one does need to tell c++ _how_ to convert the thing into
->a char* type object.
->
->class String {
->...
->     operator char* () {
->	 return _ptr;
->     }
->...
->}

     No good.  What you have done here may result in a pointer to
space that has already been released with "delete".  Think about
the sequence of events implied here.

	1.  my_func returns a string object into a temporary variable
	    allocated by the compiler. 
	2.  The "char *" conversion extracts the pointer to the array
	    of characters from the string object.  This pointer is to
	    space obtained with "new" when the string object was allocated.
	3.  The destructor for the temporary string object is called,
	    resulting in the release of the space holding the characters.
	4.  The pointer obtained in step 2, which now points to free
	    space, is passed to printf.

To make it clear that this approach is wrong, try putting code in the
destructor to clear the buffer to some garbage character before releasing
it with "delete".

					John Nagle