Sign in to follow this  
baixiangzhlt

A tricky C problem

Recommended Posts

The question is : int a=10; a+=a-=a*=a; Put these statements into a test driver and execute that program I find that the result is 0.How can it be?Can you please tell me what happens?I evaluate the result to be 100,since +=,-=and*= have the same priority.Thank you.

Share this post


Link to post
Share on other sites
The result is undefined - you can't do more than one assignment per sequence point. In this case it's probably doing

a+=a (a=20)
a-=a (a=0)
a*=a (a=0)

but you can't rely on this - the compiler could legitimately do whatever it wanted

Share this post


Link to post
Share on other sites
Quote:
Original post by cshowe
The result is undefined - you can't do more than one assignment per sequence point.


No, you forgot to do your homework, multiple assignments are very well possible; operator precedence, esp. the assignment operators are to be evaluated "from-right-to left" (contrary to the 'normal' left-to-right order).

Next time please prefix your post with "I think" or "In my humble opinion" rather than saying "It is".


a=10;
a+=a-=a*=a;


is the same as:


a+=(a-=(a*=a));


let's partition that:


a=10;
a*=a; //> a is now 100
a-=a; //> == "100-100", so after that op a is 0
a+=a; //> == "0+0", easy, not?



Hope that helps you, baixiangzhlt :)


edit: btw, what I find comfortable from time to time is a construct like this:


int res;

if( 0 != (res=foo()) ){
...
}else{
return res;
}

//> or even
if( res=bar() ){
...
}else{
return res;
}

//> this one is very useful:
x[0] = x[1] = x[2] = 0; //> saves a lot of lines and helps the compiler for SIMD

Share this post


Link to post
Share on other sites
Quote:
Original post by greenhybrid
Quote:
Original post by cshowe
The result is undefined - you can't do more than one assignment per sequence point.


No, you forgot to do your homework, multiple assignments are very well possible;


You are probably right, but for the wrong reasons.

Multiple assignments (or, more generally, modifications) per sequence point are generally evaluated in an unspecified order which is independent of operator precedence. Consider:

int a = 1;
return (a += 2) + (a *= 2);


The order of precedence is fully specified through means of parentheses, yet we do not know whether this code returns 6 or 9.

Chained assignments (as opposed to multiple assignemnts), do not have this problem. I do not know for chained modifications. The standard probably specifies how the += operators act, but I don't have it with me right now. The risk is having:
a = 1;
a += (a *= 2);

// Equivalent to:
a = (a + (a = (a * 2)));


Evaluated as:
a = (1 + (a = (1*2)); // --> a = 3


EDIT: apparently, it's undefined even for chained modifications, if you modify the same variable twice.

[Edited by - ToohrVyk on May 25, 2007 7:15:23 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by baixiangzhlt
The question is :
int a=10;
a+=a-=a*=a;
Put these statements into a test driver and execute that program I find that the result is 0.How can it be?Can you please tell me what happens?I evaluate the result to be 100,since +=,-=and*= have the same priority.Thank you.


This one seems easy enough; the compiler looks at the code as sub sections.

a + (a - (a * a))
a - (a * a)
a * a;

so looking at this, a*a = 100 (there you are correct); however a (as it now exists) would be 100 and thus would be 100 - 100 = 0. Then you take the last portion of 0 + 0 = 0 where the actual result shows.

-Root

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
Quote:
Original post by greenhybrid
Quote:
Original post by cshowe
The result is undefined - you can't do more than one assignment per sequence point.


No, you forgot to do your homework, multiple assignments are very well possible;


...

Multiple assignments (or, more generally, modifications) per sequence point are generally evaluated in an unspecified order which is independent of operator precedence. Consider:

int a = 1;
return (a += 2) + (a *= 2);


The order of precedence is fully specified through means of parentheses, yet we do not know whether this code returns 6 or 9.

...


The compiler is very well defined by the means of operator precedence, that is operator priority as well as whether the sub-statements are evaluated left-to-right or right-to-left.

Since add's go left-to-right, at first the left term (a+=2) is evaluated, and then the term on the right, (a*=2).

1) Let a=1
2) Evaluate left term, evaluate expression in brackets, have a = a+2 = 3 now
3) Evaluate right term, evaluate expression in brackets, with a=3, we have a = a*2 = 6 now


Share this post


Link to post
Share on other sites
Quote:
Original post by greenhybrid
The compiler is very well defined by the means of operator precedence, that is operator priority as well as whether the sub-statements are evaluated left-to-right or right-to-left.


Operator precedence (or associativity) has nothing to do with evaluation order—precedence only dictates how the abstract syntax should be built, it does not impose any sense of priority or timing over the various branches of that syntax tree at evaluation time.

In particular, in an expression such as a + b, a may be evaluated before or after b depending on compile-time and even runtime considerations—the standard is pretty clear on allowing compiler writers this freedom. See this FAQ entry, for instance.

Quote:
Since add's go left-to-right, at first the left term (a+=2) is evaluated, and then the term on the right, (a*=2).


I'd be curious to know where you got this information (the "add's go left-to-right" part) from in the first place. Are you sure you're not confusing this with associativity? Because adds are, indeed, left-to-right associative, but associativity has no bearing here (it only plays a role when you're adding more than three subexpressions together).

Share this post


Link to post
Share on other sites
If you take out the a-= portion of the equation, thus leaving you with

a+=a*=a;

You'll notice that the answer becomes 200. Once again proving that a+= is probably done first however the compiler must find out what a*=a is before it can add it to itself.

Share this post


Link to post
Share on other sites
Quote:
If you take out the a-= portion of the equation, thus leaving you with

a+=a*=a;

You'll notice that the answer becomes 200. Once again proving that a+= is probably done first however the compiler must find out what a*=a is before it can add it to itself.


That doesn't "prove" anything, except perhaps the behaviour of one *particular* compiler version with a *particular* set of parameters.

The behaviour of compilers doesn't matter. What matters is what the *Standard* says.

It's very simple. *ANY* side effect in an expression ins *ONLY* guaranteed by the Standard to be completely executed at the next sequence point. The value of the expression (a+=4) *will* always be a+4, BUT the value of *a* is not necessarily updated before the next sequence point. Thus, writing to a and then reading its value without a delimiting sequence point (as happens in the OP's example), will trivially yield an implementation-defined value.

Share this post


Link to post
Share on other sites
Quote:
Original post by Sharlin

That doesn't "prove" anything, except perhaps the behaviour of one *particular* compiler version with a *particular* set of parameters.


Plz find me a compiler that does not yield this value.

Share this post


Link to post
Share on other sites
Quote:
Original post by Suroot
Plz find me a compiler that does not yield this value.


All that is necessary is adding a few lines to GCC to do something else for this specific program, you know. It would still be a Standard C compiler. Also, one of my above links mentions icc, which behaves differently on the ^= operator, so why not the other ones?

Share this post


Link to post
Share on other sites
Quote:
ToohrVyk
Quote:
Original post by greenhybrid
The compiler is very well defined by the means of operator precedence, that is operator priority as well as whether the sub-statements are evaluated left-to-right or right-to-left.


Operator precedence (or associativity) has nothing to do with evaluation order—precedence only dictates how the abstract syntax should be built, it does not impose any sense of priority or timing over the various branches of that syntax tree at evaluation time.

In particular, in an expression such as a + b, a may be evaluated before or after b depending on compile-time and even runtime considerations—the standard is pretty clear on allowing compiler writers this freedom. See this FAQ entry, for instance.

Quote:
Since add's go left-to-right, at first the left term (a+=2) is evaluated, and then the term on the right, (a*=2).


I'd be curious to know where you got this information (the "add's go left-to-right" part) from in the first place. Are you sure you're not confusing this with associativity? Because adds are, indeed, left-to-right associative, but associativity has no bearing here (it only plays a role when you're adding more than three subexpressions together).


But then it would be really unintuitive if the [++*/<<>>&|...]= operators wouldn't do their jobs where there are supposed to do; every operator is like an atomic micro-program in my eyes.

At least, what I am using daily, the following fragment works fine: " (0!=(x=foo())) && x*=3; " (useless as standalone, but don't have a better example right now; btw, that style is commonly used in PHP scripts like " $value=exec() || showerror($value),die();" ).

I will surf the net, maybe I find some more info on that (maybe I should send mail to the gcc-lists ;) )


edit: screwed the quotes

Share this post


Link to post
Share on other sites
Quote:

Plz find me a compiler that does not yield this value.

That's not the point. The standard says its undefined behavior. If you write code that relies on undefined behavior willingly, and try to take the cop-out of "oh, every compiler I tried happened to yield that value in my particular test, so it must be safe," then you are a bad programmer and you have no business writing C. Or C++, where the same caveats apply.

Quote:

At least, what I am using daily, the following fragment works fine: " (0!=(x=foo())) && x*=3; " (useless as standalone, but don't have a better example right now; btw, that style is commonly used in PHP scripts like " $value=exec() || showerror($value),die();" ).

That's pretty poor code -- it's potentially rather dangerous, (EDIT: I guess that is confusing; it's dangerous from a maintainence perspective, not a sequence point issue, as && is a sequence point, unless its overloaded, which you can't do in C) and also kind of unreadable. There's no reason you can't break that out into separate statements and make it cleaner looking. Unless your compiler is braindead, writing pointless succinct code like that offers no benefits and nothing but disadvantages... and if you're compiler is braindead (old, or embedded system stuff, maybe) that's more reason not to rely on its particular implementation-defined behaviors in other cases.

Also, PHP is not C or C++, so it might handle the issue entirely different. Not all languages exhibit this behavior, after all.

Quote:

I will surf the net, maybe I find some more info on that (maybe I should send mail to the gcc-lists ;) )

You can find draft standards of the C and C++ languages online, for free (the C++ one is the easier to find, but the contents of the appropriate passages are nearly the same). They describe sequence point and evaluation order caveats. It's those you need to rely on, not empirical evidence from tests.

Share this post


Link to post
Share on other sites
Quote:
Original post by greenhybrid
But then it would be really unintuitive if the [++*/<<>>&|...]= operators wouldn't do their jobs where there are supposed to do; every operator is like an atomic micro-program in my eyes.


Well, they're not supposed to be atomic micro-programs ;)

Quote:
At least, what I am using daily, the following fragment works fine: " (0!=(x=foo())) && x*=3; "


This is fine, because it only modifies x once per sequence point (yes, && acts as a sequence point).

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
Quote:
Original post by greenhybrid
But then it would be really unintuitive if the [++*/<<>>&|...]= operators wouldn't do their jobs where there are supposed to do; every operator is like an atomic micro-program in my eyes.


Well, they're not supposed to be atomic micro-programs ;)



just an abstract view of mine :D

Quote:

Quote:
At least, what I am using daily, the following fragment works fine: " (0!=(x=foo())) && x*=3; "


This is fine, because it only modifies x once per sequence point (yes, && acts as a sequence point).


Allright: Sequence points seam to make the crop. That means the definition of C is lacky then, a dream of mine bursts ... no, not really :| still unintuitive :(

quick note to me: further investigate this topic

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
Quote:
Original post by greenhybrid
The compiler is very well defined by the means of operator precedence, that is operator priority as well as whether the sub-statements are evaluated left-to-right or right-to-left.


Operator precedence (or associativity) has nothing to do with evaluation order—precedence only dictates how the abstract syntax should be built, it does not impose any sense of priority or timing over the various branches of that syntax tree at evaluation time.


Quoted for much needed emphisis. As supporting evidence to this fact, consider the following program:

#include <iostream>

struct foo {
foo( int a ) { std::cout << a << " "; }
friend foo operator+( const foo&, const foo& ) { return foo(4); }
};

int main() {
foo(1)+foo(2)+foo(3);
std::cout << std::endl;
}


On WinXP + Visual Studio 2005, the result is:  3 2 1 4 4
On iBook + GNU C++ Compiler 4, the result is: 1 2 4 3 4


In this case, the two compilers differ in their order of evaluation. VS constructs foo(3) before evaluating foo(1)+foo(2), GCC constructs foo(3) after evaluating foo(1)+foo(2). Both are legal reinterpretations of the same bit of C++ code.

Quote:
Original post by greenhybrid
But then it would be really unintuitive if...

It seems your mistake was in assuming C was intuitive.
For the most part, it is the exact opposite.

Share this post


Link to post
Share on other sites
As a side note, running static checker on C++ code:
std::cout << x << y << z;

will warn you that this may be evaluated out of order and treat such statement as problematic.

While this is perhaps the most intuitive way, it's still not guaranteed.

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
Quote:
Original post by greenhybrid
But then it would be really unintuitive if...

It seems your mistake was in assuming C was intuitive.
For the most part, it is the exact opposite.


meh, yes :|
to clarify myself, by intuitive I meant more the meaning of "logical". Since I am coding for some years now (I began late 1999 afair, then settled from Q-Basic (...) to C/C++ in 2001), logic became intuitivity to me ... habit of mine, but if something is logical to me, it's also intuitive to me. But that's not the proper term, I agree; some highly logical stuff only old mathematicians are in (for example works about sedenions), it's definitely not intuitive to me :)

To make it short: To the day of this very thread, I allways assumed the operators do what they're supposed to do, 'executed' with respect to priority and associativity. And it was logical/intuitive to me that an assignment stores a value at the place one intended.
Unfortunately this seems to be not the case.

Thanks for the enlightenment :)

Share this post


Link to post
Share on other sites
Quote:
Original post by greenhybrid
To make it short: To the day of this very thread, I allways assumed the operators do what they're supposed to do, 'executed' with respect to priority and associativity.

Oh, they do.
They just don't tell their arguments when to execute.

Obviously, in a 'a * b + c' statement, a * b is computed before the +, because the result is needed for the addition. Priority and associativity takes care of these things just like you'd expect.
But they don't say anything about whether a, b and c themselves should be computed first. They might not be simple numbers, but could be, say, function calls. (let's call it a() * b() + c() now) Sure, we'll need to call the functions a and b before we can carry out the multiplication, but there's nothing in the rules about whether the functions a or b should be called first. Or even if c, which isn't used until a later, should be called first. But that's not the responsibility of the operators, or for that matter, their priority or associativity.

Share this post


Link to post
Share on other sites
Quote:
Original post by Spoonbender
Quote:
Original post by greenhybrid
To make it short: To the day of this very thread, I allways assumed the operators do what they're supposed to do, 'executed' with respect to priority and associativity.

Oh, they do.
They just don't tell their arguments when to execute.

Obviously, in a 'a * b + c' statement, a * b is computed before the +, because the result is needed for the addition. Priority and associativity takes care of these things just like you'd expect.
But they don't say anything about whether a, b and c themselves should be computed first. They might not be simple numbers, but could be, say, function calls. (let's call it a() * b() + c() now) Sure, we'll need to call the functions a and b before we can carry out the multiplication, but there's nothing in the rules about whether the functions a or b should be called first. Or even if c, which isn't used until a later, should be called first. But that's not the responsibility of the operators, or for that matter, their priority or associativity.


That's what I meant by 'executing'. The simple tokens "a", "b" and "c" may yield to "push a|b|c", thus the push is 'executed'. A thingie like "a+b" should yield to kind of "push a / push b / add (result in st+1) / pop".

If I feed my "xicc"-compiler (you may read more on my homepage) with the following code-fragment, ...

alpha = 1/delta + zulu;


...yields to:


push 1.000000
push delta
divp st+1, st
push zulu
addp st+1, st
movtos alpha
pop


Currently not supported by my compiler is the "+=" operator, but I would go this way:

"alpha += delta * echo"
>> push delta
>> push echo
>> mul st+1, st
>> pop
>> push alpha
>> add st, st+1
>> pop alpha
>> pop

so at first, compute/load "delta", then, compute/load "echo", multiply the left operand with the right, then load "alpha", calculate "alpha+tmpValueOnStacl", store to "alpha".

I guess it's still unclear what I mean by "execution"...in the end every terminal token in a programm yields to the insertion of some assembly, which is 'executed' (bison/flex-users know what I mean). And that's what I mean when saying "a,b,c,+=,etc." are not executed in the intended order within a sequence point. Hope you got what I mean ;)


edit: doh, messed my handcoded assembly a bit

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this