Topic: return values and destructors


Author: jimad@microsoft.com (Jim Adcock)
Date: 17 Nov 92 20:46:40 GMT
Raw View
In article <BxMxxB.CMp@scd.hp.com> vinoski@ch.apollo.hp.com (Stephen Vinoski) writes:
|There is also a similar potential problem with exceptions and throw
|statements; what happens if the value of the thrown object is affected
|by the destructors executed due to stack unwinding before the throw
|actually occurs?

I still believe you're being confused by a compiler bug.  Can you quote
ARM to support your position?  Seems clear to me that a throw expression
has to be evaluated in scope, which means it must be evaluated before
the destructors, since destructors occurs on exiting scope.  Once the
throw expression has been evaluated, the throw object *has* its value,
and the destructors can do what they want without affecting its value.

Likewise on return values.  The return expression must be evaluated in
scope which means it must be evaluated before the dtors so it must already
*have* its value by the time scope is exited and dtors are executed.
If the return object tries to get its value after dtoring its a compiler bug,
since the dtoring implies scope has already been exited so then how could
the return expression be being evaluated in scope?





Author: vinoski@ch.apollo.hp.com (Stephen Vinoski)
Date: Wed, 18 Nov 1992 14:56:36 GMT
Raw View
In article <1992Nov17.204640.9222@microsoft.com> jimad@microsoft.com (Jim Adcock) writes:
>In article <BxMxxB.CMp@scd.hp.com> vinoski@ch.apollo.hp.com (Stephen Vinoski) writes:
>|There is also a similar potential problem with exceptions and throw
>|statements; what happens if the value of the thrown object is affected
>|by the destructors executed due to stack unwinding before the throw
>|actually occurs?
>
>I still believe you're being confused by a compiler bug.  Can you quote
>ARM to support your position?  Seems clear to me that a throw expression
>has to be evaluated in scope, which means it must be evaluated before
>the destructors, since destructors occurs on exiting scope.  Once the
>throw expression has been evaluated, the throw object *has* its value,
>and the destructors can do what they want without affecting its value.

I don't think that "confusion" is the correct term since I also
believe this to be a compiler bug.  My comment about thrown objects
was an attempt to show how ridiculous it is for return expressions to
be affected by destructors of local objects.  Remember, not everyone
posting to this thread agrees that this is a problem.

I want to ensure that the interpretation of this problem as a compiler
bug is correct given what the ARM says on page 90 about how return
works.

>Likewise on return values.  The return expression must be evaluated in
>scope which means it must be evaluated before the dtors so it must already
>*have* its value by the time scope is exited and dtors are executed.
>If the return object tries to get its value after dtoring its a compiler bug,
>since the dtoring implies scope has already been exited so then how could
>the return expression be being evaluated in scope?

You're preaching to the choir. :-)  I agree with you 100%.  I just
want somebody on the ANSI C++ committee to sit up and take notice of
this problem (yes, I have already notified the rep from my company).
This behavior should be more clearly specified by the language
standard.

I will consider this to be a cfront bug and report it as such.

-steve

Steve Vinoski  (508)436-5904   vinoski@apollo.hp.com
Distributed Object Computing Program
Hewlett-Packard, Chelmsford, MA 01824       These are my opinions.




Author: jbuck@forney.berkeley.edu (Joe Buck)
Date: 18 Nov 1992 21:47:06 GMT
Raw View
In article <1992Nov17.204640.9222@microsoft.com> jimad@microsoft.com (Jim Adcock) writes:
>>I still believe you're being confused by a compiler bug.  Can you quote
>>ARM to support your position?  Seems clear to me that a throw expression
>>has to be evaluated in scope, which means it must be evaluated before
>>the destructors, since destructors occurs on exiting scope.  Once the
>>throw expression has been evaluated, the throw object *has* its value,
>>and the destructors can do what they want without affecting its value.

In article <Bxx2uD.42x@scd.hp.com> vinoski@ch.apollo.hp.com (Stephen Vinoski) writes:
>I don't think that "confusion" is the correct term since I also
>believe this to be a compiler bug.  My comment about thrown objects
>was an attempt to show how ridiculous it is for return expressions to
>be affected by destructors of local objects.  Remember, not everyone
>posting to this thread agrees that this is a problem.

Well, I was one of those who said that it wasn't a problem, because I
found the original example posted a bit contrived.  But Jim Adcock's
argument has convinced me that I was wrong; the argument to "return"
has to be evaluated before the destructors are evaluated, and the
sloppiness on this issue that I was advocating (e.g. assume nothing
about destructors that affect the return value in this way) was wrong
and would make the exceptions facility very difficult to use.

The ARM and draft standard could make this clearer.



--
Joe Buck jbuck@ohm.berkeley.edu




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Wed, 25 Nov 1992 18:15:55 GMT
Raw View
In article <1992Nov17.204640.9222@microsoft.com> jimad@microsoft.com (Jim Adcock) writes:
>In article <BxMxxB.CMp@scd.hp.com> vinoski@ch.apollo.hp.com (Stephen Vinoski) writes:
>|There is also a similar potential problem with exceptions and throw
>|statements; what happens if the value of the thrown object is affected
>|by the destructors executed due to stack unwinding before the throw
>|actually occurs?
>
>I still believe you're being confused by a compiler bug.  Can you quote
>ARM to support your position?  Seems clear to me that a throw expression
>has to be evaluated in scope, which means it must be evaluated before
>the destructors, since destructors occurs on exiting scope.  Once the
>throw expression has been evaluated, the throw object *has* its value,
>and the destructors can do what they want without affecting its value.

 If you use a silly object mayhem could result. Consider throwing
an object containing a pointer to something on the stack that gets
unwound ...

 While we're talking of unwinding the stack in exceptions,
it seems to me (help !) that any pointers on the stack will just
evaporate and the objects they point to will NOT get deleted.

 So it would seem that using 'handle' classes and not
pointers on the stack is mandatory in the presence of exceptions.
Any comments ?

--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: vinoski@ch.apollo.hp.com (Stephen Vinoski)
Date: Wed, 11 Nov 1992 23:36:07 GMT
Raw View
In section 6.6.3 on page 90 the ARM states:

"A return statement with an expression can be used only in functions
returning a value; the value of the expression is returned to the
caller of the function."

Given the statement

 return foo;

where foo is an object with some value, does the statement from the
ARM mean that the compiler *must* return the value that foo has in the
return statement, rather than the value foo might have at some point
after the destructors for local automatic objects have been executed?

Consider this example:

template<class T>
class A
{
  public:
    A(T *p) : ptr(p) {}
    ~A() {if (ptr) *ptr = 0;}
  private:
    T *ptr;
};


int
func1()
{
    int x = 10;
    A<int> a(&x);
    return x;
}


Under cfront 3.0.1, func1() returns 0, not 10, even though the value of
x in the return statement is clearly 10.  The value of x is changed to
0 due to the destructor of the A object going out of scope at the end
of the function.

What's more interesting is that if a func2() is created that returns
by value an object of a class type with a constructor and destructor,
the desired value is returned because of the generation of a
temporary:

class B
{
  public:
    B(int x) : val(x) {}
    ~B() {}
    void operator=(int x) {val = x;}
    operator int() {return val;}
  private:
    int val;
};

B
func2()
{
    B b(10);
    A<B> a(&b); // same A as above
    return b;
}


In this case, func2() returns a B whose val member has a value of 10,
due to the generation of a temporary, even though the same A
destructor sets the value to 0.

Shouldn't both of these cases perform the same way?  Is this a cfront
bug?  Shouldn't the value of the return expression be returned to the
caller regardless of side effects of destructors for auto objects?
Does the language enforce any of this?  The pre-Boston mailing doesn't
seem to discuss this any differently than the ARM does.

thanks,
-steve
--
Steve Vinoski  (508)436-5904   vinoski@apollo.hp.com
Distributed Object Computing Program
Hewlett-Packard, Chelmsford, MA 01824       These are my opinions.




Author: jbuck@forney.berkeley.edu (Joe Buck)
Date: 12 Nov 1992 02:19:32 GMT
Raw View
In article <BxKs87.417@scd.hp.com> vinoski@ch.apollo.hp.com (Stephen Vinoski) writes:
>"A return statement with an expression can be used only in functions
>returning a value; the value of the expression is returned to the
>caller of the function."
>
>Given the statement
>
> return foo;
>
>where foo is an object with some value, does the statement from the
>ARM mean that the compiler *must* return the value that foo has in the
>return statement, rather than the value foo might have at some point
>after the destructors for local automatic objects have been executed?

[ example deleted: it's designed so that the destructor of an auto
  variable will alter foo's value. ]

Your example is the kind of thing that I think language standards should
leave explicitly undefined; otherwise compiler vendors will have to
generate worse code just in case someone like you gets tricky. :-)

>Shouldn't both of these cases perform the same way?

I don't think so (if that means a change is required in the draft
standard, so be it).

>Is this a cfront bug?

Again, I don't think so.  I'm not willing to pay the cost of the
extra copy operation that might be required were the committee to
force your example to work.  It's simply not worth slowing down reasonable
C++ programs to have defined behavior on your unreasonable example (it is
cute, though).  There is already precedent: the ANSI
C standard leaves the order of side effects explicitly undefined in
examples like

 void func(int,int);
 int i = 3;
 func(i++,i++);

The ARM statement needs amendment in any case, as it says elsewhere that
the copy constructor is called to return objects, and there is no
guarantee that the user will write a proper copy constructor.





--
Joe Buck jbuck@ohm.berkeley.edu




Author: mccauleyba@vax1.bham.ac.uk (Brian McCauley)
Date: Thu, 12 Nov 1992 13:22:54 GMT
Raw View
In article <1dsevkINNe8t@agate.berkeley.edu>, jbuck@forney.berkeley.edu (Joe Buck) writes:
> In article <BxKs87.417@scd.hp.com> vinoski@ch.apollo.hp.com (Stephen Vinoski) writes:
>>"A return statement with an expression can be used only in functions
>>returning a value; the value of the expression is returned to the
>>caller of the function."
>>
>>Given the statement
>>
>> return foo;
>>
>
> [ example deleted: it's designed so that the destructor of an auto
>   variable will alter foo's value. ]
>
> Your example is the kind of thing that I think language standards should
> leave explicitly undefined; otherwise compiler vendors will have to
> generate worse code just in case someone like you gets tricky. :-)
>
  [...]

>>Is this a cfront bug?
>
> Again, I don't think so.  I'm not willing to pay the cost of the
> extra copy operation that might be required were the committee to
> force your example to work.  It's simply not worth slowing down reasonable
> C++ programs to have defined behavior on your unreasonable example (it is
> cute, though)...

I beg to differ I think that the behaviour of Cfront is a bug and that the
statement in the ARM is unambiguous if a little opaque. The redundant copy will
only be avoidable in cases where the return type is an inbuilt type and the
overhead of evaluating this before calling destructors and storing it away
somewhere is minimal. Also the return expression would have to not contain any
class types or even function calls as any class operation even on a object that
is not local may legitamately make use of local objects. (Consider a class that
has a static `population' member that is the total number of objects currently
existing. I agree the original example was contrived but I don't think it
should break a good complier.

Some compilers offer optimization switches with names like "assume no pointer
aliasing" which allow them to make this sort of optimisation at the risk of
miscompiling this sort of perverse example. I do not think a compiler should
make unsafe optimisations unless the programmer has invited it to do so.
--
    \\   ( )  NO BULLSHIT! from BAM (Brian McCauley)
 .  _\\__[oo
.__/  \\ /\@  E-mail: B.A.McCauley@bham.ac.uk
.  l___\\        Fax: +44 21 625 2175
 # ll  l\\     Snail: 197 Harborne Lane, Birmingham, B29 6SS, UK
###LL  LL\\     ICBM: 52.5N 1.9W





Author: jimad@microsoft.com (Jim Adcock)
Date: 12 Nov 92 22:47:56 GMT
Raw View
In article <BxKs87.417@scd.hp.com> vinoski@ch.apollo.hp.com (Stephen Vinoski) writes:
|
|In section 6.6.3 on page 90 the ARM states:
|
|"A return statement with an expression can be used only in functions
|returning a value; the value of the expression is returned to the
|caller of the function."

First note, that like ANSI-C, C++ does not seem to require return-by-copy,
allowing instead return-by value.  This in no way, however, excuses
compiler from making a copy of the returned value of the expression if
such a copy is required in order to maintain the correct value of that
return expression as evaluated in sequence and in scope.

|Given the statement
|
| return foo;
|
|where foo is an object with some value, does the statement from the
|ARM mean that the compiler *must* return the value that foo has in the
|return statement, rather than the value foo might have at some point
|after the destructors for local automatic objects have been executed?

Consider the not-uncommon case where foo is an auto-variable of the
function returning foo and foo is of a class with a dtor.  This common
case demonstrates that the scope that the return value expression must
the same as the scope that the return expression is in.  Otherwise foo
would be dead before evaluated.  Thus evaluation of the expression
must occur before exiting scope.  Dtoring occurs *at* exit of scope.
Thus the evaluation of the expression occurs before dtoring.  If dtoring
then affects an evaluation that must occur before dtoring, clearly
this must be a compiler bug.  If the rules were otherwise you could
not reliably return-by-value an auto-variable object of a class with a dtor.

|Consider this example:
|
|template<class T>
|class A
|{
|  public:
|    A(T *p) : ptr(p) {}
|    ~A() {if (ptr) *ptr = 0;}
|  private:
|    T *ptr;
|};
|
|
|int
|func1()
|{
|    int x = 10;
|    A<int> a(&x);
|    return x;
|}
|
|
|func1() returns 0, not 10, even though the value of
|x in the return statement is clearly 10.  The value of x is changed to
|0 due to the destructor of the A object going out of scope at the end
|of the function.

As you point out the value of x in the return statement is clearly 10.
The evaluation of the return expression then is the evaluation of x
is clearly 10.  The returned value is the evaluation of the return expression
is the evaluation of x is clearly 10.  Anything else is clearly a bug.

|What's more interesting is that if a func2() is created that returns
|by value an object of a class type with a constructor and destructor,
|the desired value is returned because of the generation of a
|temporary:
|
|class B
|{
|  public:
|    B(int x) : val(x) {}
|    ~B() {}
|    void operator=(int x) {val = x;}
|    operator int() {return val;}
|  private:
|    int val;
|};
|
|B
|func2()
|{
|    B b(10);
|    A<B> a(&b); // same A as above
|    return b;
|}
|
|
|In this case, func2() returns a B whose val member has a value of 10,
|due to the generation of a temporary, even though the same A
|destructor sets the value to 0.
|
|Shouldn't both of these cases perform the same way?

Yes.

|Is this a compiler bug?

Bug.

|Shouldn't the value of the return expression be returned to the
|caller regardless of side effects of destructors for auto objects?

Yes because clearly evaluation of an expression in scope, including
the evaluation of the expression in the return statement, are evaluated
in scope, and dtor'ing doesn't happen until exiting of scope, which is later.

|Does the language enforce any of this?

If when the spec is complete, it ought to be complete and specific enough
to make these issues unarguable.  These issues of return-by-value,
return-by-copy, etc, have been kicking around for many years in the
ANSI-C committee and now C++, and ought to just be nailed down once and
for all.




Author: vinoski@ch.apollo.hp.com (Stephen Vinoski)
Date: 13 Nov 92 03:34:23 GMT
Raw View
In article <1992Nov12.132254.1@vax1.bham.ac.uk> mccauleyba@vax1.bham.ac.uk (Brian McCauley) writes:
>I beg to differ I think that the behaviour of Cfront is a bug and that the
>statement in the ARM is unambiguous if a little opaque. The redundant copy will
>only be avoidable in cases where the return type is an inbuilt type and the
>overhead of evaluating this before calling destructors and storing it away
>somewhere is minimal. Also the return expression would have to not contain any
>class types or even function calls as any class operation even on a object that
>is not local may legitamately make use of local objects. (Consider a class that
>has a static `population' member that is the total number of objects currently
>existing. I agree the original example was contrived but I don't think it
>should break a good complier.

Actually the example is less contrived than you think.  Consider
Stroustrup's "resource acquisition is initialization" idiom mentioned
in his 2nd edition (sorry, I don't have it handy, otherwise I'd quote
chapter and page).  This idiom calls for resources (heap storage,
files, etc.) to be acquired via constructors and released via
destructors, which is a useful approach especially when exceptions are
involved.  Anyway, consider a mutex lock class that locks a mutex in
its constructor and unlocks it in its destructor:

Foo
func()
{
    static Foo foo;
    Lock lock(foo.mutex);
    // ...
    return foo;
}

In the presence of exceptions, this approach guarantees that locks are
always released.

Clearly the programmer wants the value that foo has in the return
statement to be returned to the caller.  Unfortunately, the current
language definition does not guarantee that this will happen.  What
could happen in this case is that the Lock object's destructor will
execute when it goes out of scope, and the value of foo might change
due to another thread of control before it is copied out to the
caller.  In a multi-threaded environment, this is quite possible.

There is also a similar potential problem with exceptions and throw
statements; what happens if the value of the thrown object is affected
by the destructors executed due to stack unwinding before the throw
actually occurs?

>In article <1dsevkINNe8t@agate.berkeley.edu>, jbuck@forney.berkeley.edu (Joe Buck) writes:
>Again, I don't think so.  I'm not willing to pay the cost of the
>extra copy operation that might be required were the committee to
>force your example to work.  It's simply not worth slowing down reasonable
>C++ programs to have defined behavior on your unreasonable example (it is
>cute, though).  There is already precedent: the ANSI
>C standard leaves the order of side effects explicitly undefined in
>examples like
>
> void func(int,int);
> int i = 3;
> func(i++,i++);
>

I don't see how the fact that the order of side effects within a
single statement is unspecified has anything at all to do with the
problem I've brought up.  My problem involves side effects across
multiple statements.  Aren't ANSI C "sequence points" designed to
ensure that compilers behave identically in these situations?

The "resource acquisition is initialization" approach is already very
common in C++.  In fact, the use of constructors and destructors in
this manner is typically touted as being a strong feature of the
language.  Unless the behavior of return statements vs. the
side-effects of destructors is specified by the language, I'm afraid
that this idiom becomes useless for a large class of programs.

-steve