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>