Re: Guarantee of side-effect free assignment
On Oct 7, 11:00 am, jkherci...@gmx.net wrote:
James Dennett wrote:
Alf P. Steinbach wrote:
* James Dennett:
Alf P. Steinbach wrote:
[snip]
[...] It seems the compiler is free to rewrite
p = new S();
as
p = operator new( sizeof( S ) );
new( p ) S();
What would grant the compiler freedom to deviate in such an
observable way from the semantics of the abstract machine (in
which, I hope it is clear, the rhs of an assignment is evaluated
before its result -- if there is one -- is assumed to be known).
The argument runs as follows: What we have is a composite expression
p = new S()
and the left hand side is a new expression. The "assignment", i.e., the
change of the lvalue p is a side-effect of the assignment expression. The
construction of the object is a side-effect of the new expression. The
order of the two side-effects in the evaluation of the composite expression
is not specified because there is no sequence point that separates them.
The program reaches plenty of sequence points between the evaluation
of the new-expression and the completion of the assignment operation.
As the Standard itself points out, a new expression makes a function
call (actually, several of them). And a C++ program arrives at a
sequence point whenever a called function is entered - and arrives at
another upon exit. So, based on the Standard's definition of a
sequence point:
"At certain specified points in the execution sequence called sequence
points, all side effects of previous evaluations shall be complete and
no side effects of subsequent evaluations shall have taken place."
intro.execution/7]
we can conclude that - at the point when the new-expression makes its
function call - the assignment to p must a) either be over or b) must
have not yet begun. Well, since the value assigned to "p" is dependent
on the value returned by the new-expression, the only possibility is
that the assignment to "p" must fall in the evaluations "not-yet-
started" category. Therefore we are assured that when the function
call is made, p will still have its last-assigned value (the null
pointer constant).
The C++ Standard provides an additional guarantee that no other part
of the expression (which made the function call) will be evaluated
while the function call is ongoing. So effectively, the evaluation of
the entire expression is suspended for the duration of the function
call. Meaning that p will have its original value - not only upon
function entry and exit - but at every point in between. So the
compiler could even begin to evaluate the assignment until after the
function call returns a pinter to allocated and initialized S object.
But in this example, the function never does return such a pointer -
but instead throws an exception. In short, the evaluation of the
assignment expression is over before it has even begun.
That is what grants the compiler the freedom to rewrite the expression as
p = operator new( sizeof( S ) );
new( p ) S();
provided S doesn't define operator new (in which case that one would
have to be used for the allocation, but that's just details).
Essentially, the argument being made is that because "p" may have an
intermediate value between sequence points - the compiler can rewrite
the assignment so that "p" has an intermediate value -at- a sequence
point. Specifically, the revised assignment operation has added a new
sequence point - a sequence point at which p has a value that it never
had at any sequence point in the original program's execution.
After all, at no sequence point in the original program did "p" ever
point to an allocated - but not-yet-initialized - S object. Instead
(assuming no exception was thrown) p went directly from pointing to
nothing at one sequence point to pointing to an allocated and
initialized S object at the very next sequence point. But by
separating the allocation of the S object from its initialization, the
revised assignment operation introduces a sequence point between the
two original sequence points. At this inserted sequence point, p can
be observed to point to an allocated - but not-yet-initialized - S
object. An observation not possible in the assignment as it was
originally written.
Scott Meyers and Andrei Alexandrescu have assumed[1] that the above
rewrite can only occur when the compiler can prove that S() doesn't
throw; however, they give no formal justification for this assumption.
I think the regular notions of expression evaluation cover
it; rewriting is allowed to some extent by the "as-if" rule,
but nothing grants license to make such observable changes
to the notion of evaluating an assignment f(a)=g(b). The
assignment can't take place until the result of evaluating
g is known -- and if g throws, then it *has* no result.
This reasoning assumes that the result of an expression is not known (or or
does not exist) until the side-effects of evaluating the expression have
taken place. I think that guarantee is not made anywhere in the standard.
The "as-if" rule prevents a C++ compiler from adding sequence points
with program states that are not to be found anywhere in the original
program. Granted, the Standard could contain language expressly
forbidding C++ compilers from altering the semantics of perfectly well-
defined C++ programs, especially when those changes create
opportunities for undefined behavior that simply did not exist in the
program the way it had been written. But from my point of view, that
kind of a reassurance would somehow seem more more disquieting than
helpful.
Greg
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]