【问题标题】:(C) Can someone explain to me why the code returns what it does?(C) 有人可以向我解释为什么代码会返回它的功能吗?
【发布时间】:2017-01-12 17:57:12
【问题描述】:

所以我在谈论学习 C 语言的 lynda 课程,这个例子被展示并且几乎没有解释,所以我无法理解为什么结果是这样的。记住代码不应该是正确的,我应该明白会发生什么。

#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

有人可以向我解释为什么增量返回 47,因为如果 aint x 并且 int x = 50b 是 47 因为它执行 MAX(x, increment()) 。如果我没有看错代码,它应该打印 50,因为 50 大于 47。

【问题讨论】:

  • 函数参数的求值顺序未指定。如果课程教导相关代码具有特定结果,我建议您在其他地方搜索知识。
  • 宏的行为也不如人们预期的那样。手动将宏调用替换为内容(或获取预处理输出)并使用笔和纸处理代码。宏是不是函数。永远不要在函数可以使用的地方使用宏!
  • 这样你就不会误入歧途,例如max()min() 宏,取决于参数的类型,如果任何参数值小于0,可以/将返回错误的值。

标签: c parameter-passing function-calls


【解决方案1】:

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

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

在函数指示符和实际值的评估之后有一个序列点 参数,但在实际调用之前。 调用函数中的每个求值(包括 其他函数调用)在之前或之后没有特别排序的 被调用函数体的执行顺序是不确定的 被调用函数的执行

【讨论】:

  • 即使是这种情况,并且在返回的第一行中未指定执行顺序(增量返回 47)不应该打印 50,因为 50 大于 47
  • @BogdanCrisan 怎么样? static 存储变量,初始值为42,增量为5,无论如何都会返回47。它不会累积函数参数评估产生的调用,对吧?
【解决方案2】:

这里涉及三个问题。

首先,您的increment 函数在您每次调用它时都会改变状态 - 它会总是为每次调用返回不同的值。其次是函数参数不能保证从左到右进行评估。三是宏展开后,您的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) &gt; (increment()) ? (x) : (increment())首先被求值——increment()返回47,它不大于x(50),所以表达式的结果是50。

之后的某个时间,调用了单独的 increment() 表达式,它返回 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) &gt; (increment()) ? (x) : (increment()) 首先被评估。这一次,increment() 被调用两次,在测试条件中返回57,然后在结果中返回62

当表达式 increment() 被计算时,它会被第三次调用。

所以...

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

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

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

【讨论】:

  • 谢谢,我实际上能够理解疯狂的结果是怎么回事。
【解决方案3】:

这是将函数中的副作用与多次评估参数的宏结合起来问题的一个很好的例子。

在第一个 printf 之前,我们看到从两个增量返回行中调用了两次增量。在这种情况下,MAX 宏“看到”50 和 47 并得出结论 50 更大。然后 printf 再次调用 increment 以便它“看到”50 和 52 以及 50 的宏结果

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

这解释了这段代码的奇怪输出。副作用、宏内部的多重评估以及对参数评估顺序的依赖的组合使得这个代码非常糟糕。值得在“不要这样做!”标题下进行邪恶的编码竞赛或教科书

【讨论】:

    【解决方案4】:

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

    因此,在每次调用 increment() 时,该函数将返回比上次高 5 的值,从 42+5=47 开始。

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

    x > increment() ? x : increment()
    

    由于 increment() 不仅仅是提供一个值,还包含改变它的静态变量的副作用,所以它是计算一次还是两次都非常重要。

    在这种情况下,在到达第二个 MAX 宏时,我们将 x=50 与第四次调用 increment() 的结果进行比较,即。 62. 50

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

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

    【讨论】:

      【解决方案5】:

      gcc -c -S编译源代码

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-22
        • 1970-01-01
        相关资源
        最近更新 更多