繁体   English   中英

(C)有人可以向我解释为什么代码返回其结果吗?

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

所以我在讲一本关于学习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 xint x = 50b47,因为它执行MAX(x,递增()) 如果我没有看错代码,则应打印50,因为50大于47。

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

引用C11 ,第§6.5.2.2章,函数调用,( 重点是我的

在函数指示符和实际参数的求值之后,但在实际调用之前,有一个顺序点。 相对于被调用函数的执行,不确定地对调用函数中所有未包括在被调用函数的主体执行之前或之后特定排序的评估(包括其他函数调用)进行排序

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

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

对于第二个printf,我们看到增量现在被调用了3次。 MAX宏将50与57进行比较,得出的结论是57更大,然后再次调用增量来获得62的结果。现在回到printf,我们打印50并进行另一个增量调用来获得67,当然还要返回62的宏结果。

这解释了此代码的奇怪输出。 副作用,宏内部的多次求值以及对参数求值顺序的依赖使这些代码变得非常糟糕。 值得参加邪恶的编码竞赛或教科书,标题为“不要这样做!”

这里有三个问题。

首先是您每次调用increment函数时都会更改状态-每次调用总是返回不同的值。 其次是不能保证函数参数从左到右进行求值。 第三是宏扩展后,您的printf调用如下所示:

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

因此可能会将increment称为3次。

根据您的输出, increment以下顺序调用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()) (x) > (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) > (increment()) ? (x) : (increment()) (x) > (increment()) ? (x) : (increment())首先被评估。 这次, increment() 两次被调用,在测试条件下返回57 ,然后在结果中返回62

然后,在表达式expression increment()被求值时第三次调用它。

所以...

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

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

C语言中的大多数运算符都不强制从左到右进行评估。 少数是逻辑&&|| 运营商,该?:三元运算符,并且,逗号(这是一样的参数列出了用于函数调用的逗号)。

使用静态限定符声明的函数中的变量将被静态分配,并且其范围将限于该函数(我认为),但是它将具有全局寿命。 这意味着它将在整个函数执行过程中保留其价值。 它只会被初始化一次。

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

有趣的是,宏如何影响程序执行。 如果MAX按照函数的方式工作,它将把a和b表达式求值为整数或首先比较的任何其他类型。 然后它将评估a> b? a:b表达式。 但是,由于它是一个预处理器宏,因此它所做的就是文本替换。 隐藏在混乱的宏下面的结果表达式是:

x > increment() ? x : increment()

由于增量()不仅提供值,而且还包含更改其静态变量的副作用,因此,对它进行一次还是两次评估就很重要。

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

注意:在第一个printf()中的一行中:?:MAX宏的基础运算符,最左边的操作数评估为true,因此仅评估了中间运算符,即“ x”。 这就是为什么第一个printf仅导致两次increment()的原因,而第二个printf()仅导致三个此类的调用。

编辑:当然,有关导致未指定行为的注释在右边。 尽管如此,从经验上我们还是可以看到,在实践中,编译器根据预期顺序的启发式概念执行了函数调用中的表达式。

gcc -c -S编译源代码

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM