Subject: RE: problem in C++ pointer
From: Bernard Dautrevaux <[email-masked]>
To: "'Jay Krell'" <[email-masked]>, swe sd <[email-masked]>
Cc: [email-masked]
Date: Mon, 13 Mar 2000 10:54:03 +0100

> -----Original Message-----
> From: Jay Krell [mailto:[email-masked]]
> Sent: Sunday, March 12, 2000 9:09 AM
> To: swe sd
> Cc: [email-masked]
> Subject: Re: problem in C++ pointer
> 
> 
> The behavior at a different time and place of code with 
> undefined behavior
> is not entirely relevant. Different processors, different 
> command lines,
> different optimizations (possibly processor specific), 
> different versions,
> can lead to different definitions of undefined behavior. 
> Still, sometimes
> undefined behavior is predictable.
> 
> I do not yet know if this code is undefined, but certainly 
> the outcome of
> code like
> i = i++;
> i = ++i;
> i = i--;
> i = --i;
> printf("%d%d", i, ++i);
> printf("%d%d", --i, ++i);
> etc.
> 
> is. 

NO it isn't!... In "printf("%d%d", i, ++i);" and "printf("%d%d", --i, ++i);"
you do NOT know in which order the parameters of printf are evaluated; note
that this order can be, and often is, different between both calls to
printf. 

Thus if i == 5, "printf("%d%d", i, ++i);" can display either "56" or "66",
then i == 6, then "printf("%d%d", --i, ++i);" can display either "56" or
"76" and then i is still 6. 

Note that this is the same case if you wite "j = (--i)*(++i);"; you have no
clear Idea of what you'll get :-)...

In C and C++ there is only FOUR points where sequencing of operations is
guaranteed (and only as far as the final result is concerned, not memory
accesses in some cases):
1)  Between instructions (that is at each occurence of a semicolon or
closing brace)
2)  Between the evaluation of the left-hand-side (lvalue) of an "=", "." or
"->" operator and the actual access to the refered-to lvalue (assignement to
it, or access to a member of, including call of a member function)
3)  Between the evaluation of all the parameters of a function and its call,
and between the actual use of the returned value (store or use in an
expression) and any other code (that mean that calling the function and
using the result is implicitely atomic)
4)  At short-circuit operators (|| and &&) where the left expression is
evaluated before the  right one.
5)  At each comma operator; be be VERY careful: the comma operator is NOT
the same as the comma used to separate arguments of a function call. The
comma operator is separating two expresseions that are evaluated in order
(first the left one, then the right one), then the comma operator DISCARDS
the result of the left expression and yields the value of the right one.

Apart form these cases (and for the sub-expressions of a short-circuit or
comma operator) the order of evaluation of the components of an expression
is NOT defined and may change due to very obscure reasons known only by the
compiler (as the number of registers needed to evaluate an expression or
store its result, or the need to keep some intermediate non-changing value
for further use in a subsequent instruction).

> If it isn't clear if code falls into this pattern, that's probably
> enough reason to not write code like it. But even so, if it 
> is defined, it
> is a bug if Gcc doesn't consistently give it the standard definition.

The code pattern above IS undefined, thus can give you ANY result, even the
right one :-)

> 
> Here's a good imho boiled down example:
> class X
> {
> public:
>     X* F(int i) { printf("%d", i); return this;}
> };
> 
> int i = 0;
> X x, *px = &x;
> px->F(i++)->F(i++)->F(i++);
> 
> is the expected output 012 or is it undefined?

This is undefined; 

> Could it reasonably be 000, if none of the storages occur 
> till after all
> parameters are evaluated but before any functoins are called?

It could NOT be 000, as (form C++ point of view) i++ directly change the "i"
variable, be it in memory or elsewhere; so it could be 012, 021, 120, 102,
210, or 201 but nothing else (I think its enough :-))

> 
> according to the parts of the standard I can find and decipher:
> [expr] 5.0.2: "..uses of overloaded operators are transformed 
> into function
> calls as described in 13.5. Overloaded operators obey rules for syntax
> specifidd in this clause, but the requirements of operand 
> type, lvalue, and
> evaluation order are replaced by the rules for function call".
> 
> I couldn't find the evaluation order for function calls except
> 
> [intro.execution]1.8.17 "..There is also a sequence point 
> after the copying
> of a returned value and before the execution of any 
> expressions outside the
> function". I don't know if that "copying of a returned value" 
> applies to
> pointers and references, and I find "expressions outside the function"
> unclear.

This is needed in C++ as copying the result may need implicitely calling an
user defined cast operator or copy constructor and that avoiding incorrect
evaluation order dependencies could be very difficult.

> 
> Ordinarily, the sides of a -> I don't believe have a defined order of
> evaluation:

They have one although a quite trivial one: the left side of "->" have to be
evaluated BEFORE the call can be placed, but the ARGUMENTS of the call can
be evaluated BEFORE, AFTER, or INTERMIXED with the evaluation of the left
part of "->". 

> 
> eg:
> class C
> {
> public:
> int i;
> C(int i) { this->i = i ; }
> void F(C* c) { printf("%d%d", this->i, c->i); }
> };
> 
> C carray[2] = { C(0), C(1) };
> C* pc = carray;
> 
> (pc++)->F(pc++);
> 
> Is the output defined?

No, the order of the two "pc++" is undefined.

> It could "reasonably" be
> 01
> 10

Yes

> 00

I don't think so; this would mean parallelizing the two "p++", thus breaking
their implicit character of atomicity.
 
> ?
> 
> I'm assuming the order of eval of an unoverloaded -> is the same, if
> defined, as the order of eval of ".".

Yes.

> 
> "sequence point" is not in the index..
> 
>  - Jay
> 

Note that all this is quite intricated and was explicitely left undefined to
allow the optimizer to do its work. There is only one rule to follow: NEVER
modify the same variable twice in the same instruction.

If you NEED such a construct, introduce intermediate variables and remove
the offending order dependency. Note that IIRC gcc with "-Wall" will warn
you of these incorrect order dependencies (among lots of other dubious
constructs ;->).

Hope this helps,

		Bernard

--------------------------------------------
Bernard Dautrevaux
Microprocess Ingéniérie
97 bis, rue de Colombes
92400 COURBEVOIE
FRANCE
Tel:	+33 (0) 1 47 68 80 80
Fax:	+33 (0) 1 47 88 97 85
e-mail:	[email-masked]
		[email-masked]
-------------------------------------------- 

--
Want to unsubscribe from this list?
Send a message to [email-masked]