Path: utzoo!utgpu!news-server.csri.toronto.edu!bonnie.concordia.ca!uunet!motcid!stephens From: stephens@motcid.UUCP (Kurt Stephens) Newsgroups: comp.lang.c++ Subject: GC, TC++ 1.01 calls X::X(X& (*this))! Keywords: GC, garbage collection, TC++, c++, copy constructors Message-ID: <6309@celery34.UUCP> Date: 22 Jan 91 22:41:23 GMT Distribution: comp Organization: Motorola Inc., Cellular Infrastructure Div., Arlington Heights, IL Lines: 225 I know this is kinda long, but... Has anybody else seen TC++ 1.01 generating code where a copy constructor makes a copy of this'self? I ran into this problem when writing a small Lisp interpreter. All Lisp object (class T and its derived classes) pointers are wrapped by an LVAL class which increments reference count in the pointed-to object, and decrements the reference count (and possibly GCs (deletes) the un-referenced object) during the LVAL::~LVAL(). -------------------------cut here------------------------------- #include #define DEBUG class T { friend class LVAL; int refcount; // number of LVAL refs to this. public: T() { #ifdef DEBUG cerr << "T::~(): " << (void*) this << '\n'; #endif refcount = 0; } virtual ~T() { #ifdef DEBUG cerr << "T::~T(): " << (void*) this << '\n'; #endif if ( refcount > 0 ) cerr << "T::~T(): this = " << (void*) this << ": " << refcount << " dangling reference(s)\n"; } int refs() { return refcount; } virtual void print ( ostream& os ) { os << "T"; } }; class Nil : public T { public: Nil() {} void print ( ostream& os ) { os << "NIL"; } }; class LVAL { T* p; void ref(T* ptr) { p = ptr; p->refcount ++; } void unref() { if ( p != NULL && -- (p->refcount) <= 0 ) delete p; p = NULL; } public: LVAL () { #ifdef DEBUG cerr << "LVAL::LVAL(): " << (void*) this << '\n'; #endif p = NULL; } LVAL ( T* p ) { #ifdef DEBUG cerr << "LVAL::LVAL(T* p): " << (void*) this << ": " << (void*) p << '\n'; #endif ref(p); } ~LVAL () { #ifdef DEBUG cerr << "LVAL::~LVAL(): " << (void*) this << '\n'; #endif unref(); } // // THE CULPRIT!! // LVAL ( LVAL& lval ) { #ifdef DEBUG cerr << "LVAL::LVAL(LVAL& lval): " << (void*) this << ": " << (void*) &lval << '\n'; if ( this == &lval ) cerr << "LVAL::LVAL(LVAL& lval): " << (void*) this << ": this == &lval: compiler error\n"; #endif ref(lval.p); } LVAL& operator = ( LVAL& lval ) { unref(); ref(lval.p); return *this; } T& operator() () { return *p; } #define CAST(x,t) ((t&)((x)())) void print ( ostream& os ) { p->print(os); } friend ostream& operator << ( ostream& os, LVAL& lval ) { #ifdef DEBUG os << ((void*) &lval) << "->" << (void*) lval.p << ':' << lval.p->refs() << ':'; #endif lval.print(os); return os; } }; class Cons : public T { LVAL car, cdr; public: Cons () : car(new Nil), cdr(new Nil) {} Cons ( LVAL& CAR, LVAL& CDR ) : car(CAR), cdr(CDR) {} LVAL CAR() { return car; } LVAL CDR() { return cdr; } void print ( ostream& os ) { os << '(' << car << " . " << cdr << ')'; } }; // // other classes, functions. // // // WARNING: // No dynamic type checking!!! BE CARFUL!! // LVAL car (LVAL& cons) { return CAST(cons,Cons).CAR(); } LVAL cdr (LVAL& cons) { return CAST(cons,Cons).CDR(); } #define p(x) cout << "x" << " = " << (x) << '\n' main() { LVAL A = new T; p(A); LVAL B = new Nil; p(B); LVAL C = new Cons ( A, B ); p(C); p(car(C)); p(cdr(C)); LVAL D; p(D = C); } -------------------------cut here------------------------------- Now implement some other functions return and pass LVAL instances. (deeply nested calls, please ;^) The problem is that sometimes LVAL::LVAL(LVAL& lval) is called with this == &lval! LVAL::LVAL(LVAL& lval) increments this->p->refcount, which is already incremented for this instance of LVAL. LVAL::~LVAL() is only called once, p->refcount never goes back to 0, so garbage isn't collected. (I know this is pretty primative GC, but it works, when it works! ;^) Obviously, there is a simple kludge: LVAL (LVAL& lval) { if ( this != &lval ) ref(lval.p); } But, the point is that a class instance should not construct an instance from itself. Right?. Now, I don't know if the above example will produce the funky calls LVAL::LVAL(LVAL&), (I'm reproducing this from my noodle). I'll try to come back with a definite (short) example. Maybe it has something to do with CC's for return values. I've implemented it using inline and non-inline functions, both with the same problems. Could some people place runtime checks in some real working TC++ classes to check my suspicions, like: X::X(X&x) { if ( this == &x ) cerr << "X::X(X&): this == &x\n"; } Has anybody seen this happen before? Are there any other implementations that do this? This program works fine on Sun 2.0: T::~(): 0x203ac LVAL::LVAL(T* p): 0xefffcb8: 0x203ac A = 0xefffcb8->0x203ac:1:T T::~(): 0x20bc0 LVAL::LVAL(T* p): 0xefffcb4: 0x20bc0 B = 0xefffcb4->0x20bc0:1:NIL T::~(): 0x20bcc LVAL::LVAL(LVAL& lval): 0x20bd4: 0xefffcb8 LVAL::LVAL(LVAL& lval): 0x20bd8: 0xefffcb4 LVAL::LVAL(T* p): 0xefffcac: 0x20bcc C = 0xefffcac->0x20bcc:1:(0x20bd4->0x203ac:2:T . 0x20bd8->0x20bc0:2:NIL) LVAL::LVAL(LVAL& lval): 0xefffca0: 0x20bd4 car(C) = 0xefffca0->0x203ac:3:T LVAL::LVAL(LVAL& lval): 0xefffc9c: 0x20bd8 cdr(C) = 0xefffc9c->0x20bc0:3:NIL LVAL::~LVAL(): 0xefffc9c LVAL::~LVAL(): 0xefffca0 LVAL::LVAL(): 0xefffc98 D = C = 0xefffc98->0x20bcc:2:(0x20bd4->0x203ac:2:T . 0x20bd8->0x20bc0:2:NIL) LVAL::~LVAL(): 0xefffc98 LVAL::~LVAL(): 0xefffcac LVAL::~LVAL(): 0x20bd8 LVAL::~LVAL(): 0x20bd4 T::~T(): 0x20bcc LVAL::~LVAL(): 0xefffcb4 T::~T(): 0x20bc0 LVAL::~LVAL(): 0xefffcb8 T::~T(): 0x203ac P.S.: Put this one in the TC++ bug list. Kurt A. Stephens Foo::Foo(){return Foo();} stephens@void.rtsg.mot.com "When in doubt, recurse." -- Kurt A. Stephens Foo Foo::Foo(){return Foo();} stephens@void.rtsg.mot.com "When in doubt, recurse."