(C) 有人可以向我解释代码 returns 的作用吗?

(C) Can someone explain to me why the code returns what it does?

所以我在讲一个关于学习 C 语言的 lynda 课程,这个例子被展示并且几乎没有解释,所以我无法理解为什么结果是他们 were.Keep 记住的代码不应该准确地说,我只是应该了解会发生什么。

#include <stdio.h>

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )

int increment() {
    static int i = 42;
    i += 5;
    printf("increment returns %d\n", i);
    return i;
}

int main( int argc, char ** argv ) {
    int x = 50;
    printf("max of %d and %d is %d\n", x,increment(), MAX(x, increment()));
    printf("max of %d and %d is %d\n", x,increment(), MAX(x, increment()));
    return 0;
}

结果是:

increment returns 47
increment returns 52
max of 50 and 52 is 50
increment returns 57
increment returns 62
increment returns 67
max of 50 and 67 is 62

有人可以向我解释为什么增加 returns 47 因为如果 aint x int x = 50b47 因为它执行 MAX(x, increment())。如果我没有读错代码,它应该打印 50,因为 50 大于 47。

正如我所见,这将是未指定的行为,因为未指定函数参数的执行/评估顺序。

引用 C11,章节 §6.5.2.2,函数调用,(强调我的

There is a sequence point after the evaluations of the function designator and the actual arguments but before the actual call. Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function.

这是将函数中的副作用与多次计算参数的宏相结合的问题的一个很好的例子。

在第一个 printf 中,我们看到 increment 在两个 increment returns 行中被调用了两次。在这种情况下,MAX 宏 "sees" 50 和 47 并得出 50 更大的结论。然后 printf 再次调用 increment 所以它 "sees" 50 和 52 以及 50

的宏结果

对于第二个 printf,我们看到 increment 现在被调用了三次。 MAX 宏比较 50 和 57,得出 57 更大的结论,然后它再次调用 increment 得到结果 62。现在回到 printf,我们打印 50 和另一个 increment 调用得到 67,当然还有 62 的宏结果。

这解释了这段代码的奇怪输出。副作用的组合、宏内的多重评估以及对参数评估顺序的依赖使这段代码变得非常糟糕。在 "Don't do this!"

标题下值得参加邪恶的编码竞赛或教科书

一个函数内的变量,用 static 限定符声明,将被静态分配,虽然它的范围将限于这个函数(我认为),但它会有一个全局的生命周期。这意味着,它将在整个函数执行过程中保留它的价值。只会初始化一次。

因此,每次调用 increment() 时,函数都会 return 比上次高 5,从 42+5=47 开始。

有趣的是宏如何影响您的程序执行。如果 MAX 以函数的方式工作,它会将 a 和 b 表达式计算为整数或它首先比较的任何其他类型。然后它会评估 a>b ? a : b 表达式。然而,由于它是一个预处理器宏,它所做的只是文本替换。隐藏在令人困惑的宏下面的结果表达式是:

x > increment() ? x : increment()

由于 increment() 不仅提供一个值,而且还包含更改其静态变量的副作用,因此无论是计算一次还是两次都非常重要。

在这种情况下,在到达第二个 MAX 宏时,我们将 x=50 与第四次调用 increment() 的结果进行比较,即。 62. 由于 50 < 62,我们然后评估最右边的表达式,即 increment() 的第五次调用,returns 67,然后由 ?: 运算符 returned 到 printf()。

注意:上面一行,在第一个 printf() 中,在 ?: MAX 宏的底层运算符中,最左边的操作数被评估为真,所以只有中间的运算符被评估,即 "x"。这就是为什么第一个 printf 只导致两次 increment() 调用,而第二个 printf() 导致三个这样的调用。

编辑:当然,关于此导致未指定行为的评论是正确的。尽管如此,我们凭经验看到,在实践中,您的编译器产生的执行符合函数调用中表达式执行的预期顺序的直观概念。

这里有三个问题。

首先是您的 increment 函数在您每次调用它时都会改变状态 - 它会 总是 return 每次调用都有不同的值。其次是函数参数不能保证被评估left-to-right。第三是宏扩展后,您的 printf 调用如下所示:

printf("max of %d and %d is %d\n", x,increment(), (x) > (increment()) ? (x) : (increment()));

所以increment有可能被调用3次。

根据您的输出,increment 的调用顺序如下:

printf("max of %d and %d is %d\n", x, increment(), (x) > (increment()) ? (x) : (increment()));
                                      ^                   ^
                                      |                   |
                                      |                   +---- increment returns 47
                                      +------------------------ increment returns 52

即先对表达式(x) > (increment()) ? (x) : (increment())求值-increment()returns 47,不大于x(50),所以结果表达式的值为 50.

在那之后的某个时候,单独的 increment() 表达式被调用,returns 52.

通过第二次 printf 调用,我们得到

printf("max of %d and %d is %d\n", x, increment(), (x) > (increment()) ? (x) : (increment()));
                                      ^                   ^                     ^
                                      |                   |                     +---- increment returns 62
                                      |                   +-------------------------- increment returns 57
                                      +---------------------------------------------- increment returns 67

同样,首先计算 (x) > (increment()) ? (x) : (increment())。这次,increment() 被调用 两次 ,return 在测试条件中调用 57,然后在结果中调用 62

然后在计算表达式 increment() 时第三次调用它。

所以...

处理此问题的最佳方法是将increment的结果分配给一个临时的,并在printf调用中使用该临时的:

int tmp = increment();
printf( "max of %d and %d is %d\n", x, tmp, MAX(x, tmp) );

C 中的大多数运算符不强制从左到右求值。为数不多的是逻辑 &&|| 运算符、?: 三元运算符和 , 逗号运算符(即 not 与函数调用的参数列表中使用的逗号相同)。

gcc -c -S

编译源代码

你会得到一个.s文件,它是汇编语言

然后您可以看到 .s 文件中真正发生了什么,我怀疑是因为 #define MAX 是一个 ,在编译时它会扩展为内联代码。 我在大学里学过 C,那是几年前的事了,这是我可以在不仔细检查汇编语言的情况下给出的最佳答案。出于诸如此类的原因,我避免使用宏...因为如果您不完全了解编译器如何将宏扩展为内联代码,它可能会导致问题。虽然宏可以为您节省一些源代码的输入,而且看起来很漂亮,但如果代码执行产生错误的答案,那将毫无意义。