C、pre和post自增,不同程序答案不同

C, pre and post increment, different answers in different programs

在学习C语言的过程中测试了一些代码,

#include <stdio.h>
#include <math.h>
#define hypotenusa(x, y)  sqrt((x) * (x) + (y) * (y))

int main(void) {
    int a, x;
    x = 2;
    a = hypotenusa(++x, ++x);
    printf("%d\n", a);
}

我正在得到答案

任何人都可以解释这种行为吗? 我的逻辑是应该 6 (sqrt(42)) 但是...

undefined behaviour

宏替换后

a = hypotenusa(++x, ++x);

变成:

a = sqrt((++x) * (++x) + (++x) * (++x));

如您所见,x 被多次修改,没有任何中间序列点。参见 What Every C Programmer Should Know About Undefined Behavior

hypotenusa(++x, ++x) 是未定义的行为。
由编译器决定先递增(和压入)哪个参数 - 在宏展开后,共有四个实例,并且未定义序列。

切勿在同一条语句中多次递增同一个变量,以避免此类问题。在宏中使用变量两次可以隐藏此错误并使其变得非常讨厌。

您的宏的行为未定义,这意味着 任何 结果都是可能的。

Chapter and verse

6.5 Expressions
...
2     If a side effect on a scalar object is unsequenced relative to either a different side effect
      on the same scalar object or a value computation using the value of the same scalar object,
      the behavior is undefined. If there are multiple allowable orderings of the subexpressions
      of an expression, the behavior is undefined if such an unsequenced side effect occurs in any
      of the orderings.84)
84) This paragraph renders undefined statement expressions such as
    i = ++i + 1;
    a[i++] = i;
      while allowing
    i = i + 1;
    a[i] = i;

基本上,如果您在一个表达式中多次修改一个对象的值,或者同时修改一个对象并在表达式的计算中使用它的值,除非有一个序列点 在这些操作之间。除了少数例外,C 不强制对表达式或函数参数求值进行从左到右的求值,也不强制在求值后立即应用 ++-- 的副作用。因此,像 x++ * x++ 这样的表达式的结果会因平台、程序和程序而异,甚至可能会因 运行 运行 而异(尽管我在实践中从未见过)。

例如,给定表达式 y = x++ * x++,可能有以下计算序列:

t0 <- x        // right hand x++, t0 == 2
t1 <- x        // left hand x++, t1 == 2
y <- t0 * t1   // y = 2 * 2 == 4
x <- x + 1     // x == 3
x <- x + 1     // x == 4

如果您假设从左到右求值,这不会给您预期的结果。