A tricky C problem

Started by
19 comments, last by greenhybrid 16 years, 11 months ago
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.
Advertisement
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?
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
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.
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).

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
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 4On 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.
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.
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 :)
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.

This topic is closed to new replies.

Advertisement