Topic: Meaning of "side effects
Author: sdm@cs.brown.edu (Scott Meyers)
Date: Tue, 31 Mar 1992 01:22:03 GMT Raw View
I received this from a friend who can't post. It's motivated by the
example in "Effective C++," p. 42:
class Wacko {
private:
String s1, s2;
public:
Wacko(char* s) : s1(s), s2(0) {}
Wacko(const Wacko& x) : s2(x.s1), s1(0) {}
};
Wacko w1 = "Hello, world";
Wacko w2 = w1;
This seemingly innocuous example has opened up a whole new can of worms
for me and my home-brew compiler.
The above example is used to explain why all ctors initialize all member
objects in the order of declaration. It also points out a tricky little
gotcha for compiler writers. If the above example is translated into
something like the following:
Wacko tmp("Hello, world"); // tmp.s1="Hello world", tmp.s2 = 0
Wacko w1(tmp); // w1.s1 = 0, w1.s2="Hello world"
Wacko w2(w1); // w2.s1 = 0; w2.s2 = w1.s1 = 0
You get one result (w2.s1 == w2.s2 == 0). But if the compiler tries to
save a step or two by optimizing out a temporary, you have the following:
Wacko w1("Hello, world"); // w1.s1="Hello world", w1.s2 = 0
Wacko w2(w1); // w2.s1 = 0; w2.s2 = w1.s1 = "Hello ..."
which yields quite a different end result (namely, w2.s2 = "Hello ...",
rather than 0).
Since your copy-ctor places s1 into s2 and puts a 0 in s1, calling it
once will zero out whatever was in s1 and calling it again will copy this
zero from s1 into s2, effectively zeroing it out as well. Aside from the
fact that this is somewhat counterintuitive, this example has some fairly
far reaching consequences for agressive compiler optimization w/ respect
to elimination of temporaries in a typical separate compilation
environment.
The ARM sez that the use of such temps is implementation dependent (12.2).
From the ARM, section 12.1c, the rule for when the compiler is free to
optimize these temps out is as follows:
"... the introduction of a temporary object and the calls of its
ctor/dtor pair may be eliminated if the only way the user can detect
its elimination or introduction is by observing side effects generated
by the ctor or dtor calls."
This draws into question what is meant by "side effects". What I
originally envisioned when reading the rule was print statements in the
ctor/dtor. On the other hand, it is possible to view the
initialization/transposition of values as a side-effect of the copy ctor.
Given this, the resulting differences in the end result are just an
implementation defined side effect.
If it is not required of the compiler to detect this scenario, and it is
also not *required* of the compiler to optimize these temps out, we are
left with the situation where simple code such as this will behave
differently when moved from compiler to compiler, or when compiled w/ or
w/o the optimizer.
I think that the compiler needs to detect this scenario, and that the
transposition of values is *not* what was meant by "side effect".
cfront 2.0 performs this tmp elimination for your example code snipet.
Another compiler (mine) does not optimize the tmp out, and different
values result. Your ctors are inline, so the compiler theoretically can
(and should) detect this scenario and realize that it cannot eliminate
the temporary. But if the definition for these 2 ctors was in a
different translation unit, the typical separate compilation environment
has no way of telling whether the ctor it is calling is implemented in
such a pathological fashion or not. So, the compiler must make worst
case assumptions, which means more often than not, this "elimination of
temp" optimization is not safe. Unless all your copy ctors are inline or
until we have more sophisticated environments for separate compilation, I
believe this optimization is not doable, even if someone can come up with
a good method for detecting these scenarios.
Comments?
Scott
-------------------------------------------------------------------------------
What do you say to a convicted felon in Providence? "Hello, Mr. Mayor."
Author: Bruce.Hoult@bbs.actrix.gen.nz
Date: Tue, 31 Mar 1992 13:57:06 GMT Raw View
In article <1992Mar31.012203.21833@cs.brown.edu> sdm@cs.brown.edu (Scott Meyers) writes:
> I received this from a friend who can't post. It's motivated by the
> example in "Effective C++," p. 42:
>
> class Wacko {
> private:
> String s1, s2;
> public:
> Wacko(char* s) : s1(s), s2(0) {}
> Wacko(const Wacko& x) : s2(x.s1), s1(0) {}
> };
>
> Wacko w1 = "Hello, world";
> Wacko w2 = w1;
>
> This seemingly innocuous example has opened up a whole new can of worms
> for me and my home-brew compiler.
There's no problem here. This is simply a complete perversion of what copy
constructors are intended to do -- namely make a semantically (instead of
simply bitwise) identical copy of an object.
--
Bruce.Hoult@bbs.actrix.gen.nz Twisted pair: +64 4 477 2116
BIX: brucehoult Last Resort: PO Box 4145 Wellington, NZ
"Cray's producing a 200 MIPS personal computer with 64MB RAM and a 1 GB
hard disk that fits in your pocket!" "Great! Is it PC compatable?"
Author: mrs@charlie.secs.csun.edu (Mike Stump)
Date: Tue, 31 Mar 1992 23:08:43 GMT Raw View
In article <1992Mar31.012203.21833@cs.brown.edu> sdm@cs.brown.edu (Scott Meyers) writes:
>I received this from a friend who can't post. It's motivated by the
>example in "Effective C++," p. 42:
>
> class Wacko {
> private:
> String s1, s2;
> public:
> Wacko(char* s) : s1(s), s2(0) {}
> Wacko(const Wacko& x) : s2(x.s1), s1(0) {}
> };
>
> Wacko w1 = "Hello, world";
> Wacko w2 = w1;
>
> This seemingly innocuous example has opened up a whole new can of worms
> for me and my home-brew compiler.
No new worms, honest.
> The above example is used to explain why all ctors initialize all member
> objects in the order of declaration. It also points out a tricky little
> gotcha for compiler writers. If the above example is translated into
> something like the following:
It is not a gotcha for compiler writers. It is a gotcha for people
that write copy contructors that aren't, hum, I mean, that do weird
things.
> You get one result (w2.s1 == w2.s2 == 0). But if the compiler tries to
> save a step or two by optimizing out a temporary, you have the following:
> which yields quite a different end result (namely, w2.s2 = "Hello
> ...", rather than 0).
>
> Aside from the fact that this is somewhat counterintuitive, this
> example has some fairly far reaching consequences for aggressive
> compiler optimization w/ respect to elimination of temporaries in a
> typical separate compilation environment.
Yes, this is counterintuitive because of the (incorrect) copy
constructor. No, it does not have any far reaching consequences.
> If it is not required of the compiler to detect this scenario, and
> it is also not *required* of the compiler to optimize these temps
> out, we are left with the situation where simple code such as this
> will behave differently when moved from compiler to compiler, or
> when compiled w/ or w/o the optimizer.
It is true, the compiler is not required to detect these cases. Your
concern is founded. You must provide meaningful copy constructors or
you will get weird results.
> I think that the compiler needs to detect this scenario, and that
> the transposition of values is *not* what was meant by "side
> effect".
No, I do not believe it should. The transposition semantics are
created by YOUR code, the problem is therefore yours, I believe.
> cfront 2.0 performs this tmp elimination for your example code
> snipet.
>
> Another compiler (mine) does not optimize the tmp out, and
> different values result. Your ctors are inline, so the compiler
> theoretically can (and should) detect this scenario and realize
> that it cannot eliminate the temporary. But if the definition for
> these 2 ctors was in a different translation unit, the typical
> separate compilation environment has no way of telling whether the
> ctor it is calling is implemented in such a pathological fashion or
> not. So, the compiler must make worst case assumptions, which
> means more often than not, this "elimination of temp" optimization
> is not safe. Unless all your copy ctors are inline or until we
> have more sophisticated environments for separate compilation, I
> believe this optimization is not doable, even if someone can come
> up with a good method for detecting these scenarios.
Why do you think it cannot eliminate the temporary? Just because you
have a funny copy constructor? I think it is beyond the scope of the
compiler to analyze the semantics of your copy constructor, and warn
when you do odd things.
For example the program:
class C {
C(const C& o) {
abort();
}
C() {
}
} a;
main() {
return 0;
}
I would argue, is free to abort, or not, as it sees fit. I think the
unqualified statement, ``Such introduction of temporaries is
implementation dependent.'' gives it the freedom.
--
If I can get mail to you via a legally registered fully qualified
domain name, you could be on Saturn for all I care.
-- quote by Bob Sutterfield <bob@MorningStar.Com>