The code is undefined.
The macro invocation COMP(x++, ++y)
expands to
( ((x++) * (x++)) + ((++y) * (++y)) )
When we have an expression or subexpression like
x++ * x++
where the same variable, x
, is modified twice, there is no rule to say which modification happens first. (And before you ask: no, the parentheses don't change anything, and neither does precedence.) For this reason, compilers don't have to do it one way or another, and this is what makes the code undefined: compilers don't have to do anything reasonable at all, because you're not supposed to write code that's undefined in the first place.
For the full story, see the canonical question Why are these constructs using pre and post-increment undefined behavior?
This is one of the reasons why "function-like" macros such as COMP()
must be used carefully, or not at all. There are basically three rules:
- Parenthesize all the arguments in the macro definition
- Parenthesize the whole definition
- In general, don't invoke the macro on arguments with side effects
The COMP
macro's definition follows rules 1 and 2, but the invocation COMP(x++, ++y)
obviously violates rule 3.
See also question question 10.1 in the old C FAQ list.
For most if not all uses of function-like macros, inline functions are preferable. If you had written
inline int comp(int x, int y) { return x*x + y*y; }
I think you would have gotten a more sensible result. Inline functions are required to evaluate their arguments just as regular function calls do, so in this case when you call comp(x++, ++y)
you get just one x++
and one ++y
, meaning there's no undefined behavior, and a well-defined result.
As an additional point of possible interest, I've seen a version of this problem come up in production code, in a surprisingly natural, uncontrived situation. Imagine you're writing a little stack-based arithmetic evaluator. Ignoring stack overflow detection for the moment, you might start with something like
int stack[50];
int *stackp = stack;
#define Push(val) (*stackp++ = (val))
#define Pop() (*--stackp)
Then you can push some numbers on the stack with code like
Push(10);
Push(20);
Then you can implement arithmetic operators which pop numbers off the stack, perform an operation, and push the result. Here's addition:
int x = Pop();
int y = Pop();
Push(x + y);
But almost immediately you realize, Who needs those temporary variables?, and you "simplify" it to
Push(Pop() + Pop());
But this Does Not Work, it gives you strange results, and the reason is that it expands to
(*stackp++ = ((*--stackp) + (*--stackp)));
which is mondo undefined. (I remember helping a co-worker track this exact problem down in his code in 2003 or so.)