Path: utzoo!telly!attcan!utgpu!jarvis.csri.toronto.edu!rutgers!ucsd!usc!csun!mx!cbcscmrs From: mx!cbcscmrs@csun.edu Newsgroups: gnu.g++ Subject: Re: Destructor called too soon, Destructor not called - G++1.35 Message-ID: <2381@csun.edu> Date: 5 Sep 89 02:58:16 GMT References: <8909040747.AA00164@teacake.sun.com> Sender: news@csun.edu Reply-To: cbcscmrs@ma.csun.edu (Mike Stump) Distribution: gnu Organization: CSU, Northridge School of Engineering & Computer Science Lines: 205 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 probably for luck--see below. ! #include ! !//** 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 !//** 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