代码是未定义。
宏调用COMP(x++, ++y) 扩展为
( ((x++) * (x++)) + ((++y) * (++y)) )
当我们有一个表达式或子表达式时
x++ * x++
当同一个变量x 被修改两次时,没有规则说哪个修改首先发生。 (在你问之前:不,括号不会改变任何东西,优先级也不会。)因此,编译器不必以一种或另一种方式来做,这就是使代码未定义的原因:编译器不'根本不需要做任何合理的事情,因为你不应该首先编写未定义的代码。
有关完整故事,请参阅规范问题Why are these constructs using pre and post-increment undefined behavior?
这就是为什么必须小心使用COMP() 等“类函数”宏的原因之一,或者根本不使用。基本上有三个规则:
- 将宏定义中的所有参数括起来
- 将整个定义用括号括起来
- 一般来说,不要在有副作用的参数上调用宏
COMP 宏的定义遵循规则 1 和 2,但调用 COMP(x++, ++y) 显然违反了规则 3。
另请参阅旧的C FAQ list 中的问题question 10.1。
对于大多数(如果不是全部)类函数宏的使用,内联函数是更可取的。如果你写了
inline int comp(int x, int y) { return x*x + y*y; }
我认为你会得到一个更明智的结果。内联函数需要像常规函数调用一样评估它们的参数,因此在这种情况下,当您调用 comp(x++, ++y) 时,您只会得到一个 x++ 和一个 ++y,这意味着没有未定义的行为,并且是明确定义的结果.
作为另一个可能的兴趣点,我已经看到这个问题的一个版本出现在生产代码中,在一种令人惊讶的自然、非人为的情况下。想象一下,您正在编写一个基于堆栈的小型算术评估器。暂时忽略堆栈溢出检测,您可能会从类似的东西开始
int stack[50];
int *stackp = stack;
#define Push(val) (*stackp++ = (val))
#define Pop() (*--stackp)
然后你可以使用类似的代码将一些数字压入堆栈
Push(10);
Push(20);
然后您可以实现算术运算符,将数字从堆栈中弹出,执行操作并推送结果。这是补充:
int x = Pop();
int y = Pop();
Push(x + y);
但您几乎立即意识到,谁需要这些临时变量?然后您将其“简化”为
Push(Pop() + Pop());
但这不起作用,它会给你奇怪的结果,原因是它扩展为
(*stackp++ = ((*--stackp) + (*--stackp)));
这是 mondo 未定义的。 (我记得在 2003 年左右帮助一位同事在他的代码中追踪这个确切的问题。)