Article View: gnu.g++
Article #113Destructor called too soon, Destructor not called - G++1.35
From: Michael Tiemann
Date: Mon, 04 Sep 1989 07:47
Date: Mon, 04 Sep 1989 07:47
172 lines
7153 bytes
7153 bytes
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
Technical Details
Message-ID:
<8909040747.AA00164@teacake.sun.com>Path:
arc1-ca.pugleaf.net!utzoo!utgpu!jarvis.csri.toronto.edu!rutgers!ukma!tut.cis.ohio-state.edu!SUN.COM!tiemannReferences:
<ANDREWT.89Aug3001106@watsnew.waterloo.edu>Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!utzoo!utgpu!jarvis.csri.toronto.edu!rutgers!ukma!tut.cis.ohio-state.edu!SUN.COM!tiemann From: tiemann@SUN.COM (Michael Tiemann) Newsgroups: gnu.g++ Subject: Destructor called too soon, Destructor not called - G++1.35 Message-ID: <8909040747.AA00164@teacake.sun.com> Date: 4 Sep 89 07:47:33 GMT References: <ANDREWT.89Aug3001106@watsnew.waterloo.edu> Sender: daemon@tut.cis.ohio-state.edu Reply-To: tiemann@lurch.stanford.edu Distribution: gnu Organization: GNUs Not Usenet Lines: 172