简体   繁体   中英

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

So I was talking a lynda course on learning the C language and this example was shown and barely explained so I was unable to understand why there results were what they were.Keep in mind the code isn't supposed to be correct, I'm just supposed to understand what happens.

#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;
}

and the result is:

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

Can someone explain to me why increment returns 47 because if a is int x and int x = 50 and b is 47 because it executes MAX(x, increment()) . If I'm not reading the code wrong it should print 50 because 50 is greater than 47.

As I see , this would be unspecified behaviour, as the order of execution / evaluation of function arguments is not specified.

Quoting C11 , chapter §6.5.2.2, Function calls, ( emphasis mine )

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 .

This is a good example of the problem of combining side effects in a function with macros that evaluate parameters multiple times.

Leading to the first printf we see that increment is called twice from the two increment returns lines. In this case, the MAX macro "sees" 50 and 47 and concludes that 50 is greater. The printf then calls increment again so it "sees" 50 and 52 and the macro result of 50

For the second printf we see that increment is now called three times. The MAX macro compares 50 and 57 and concludes 57 is greater, it then calls increment again to get the result of 62. Now back to printf, we print 50 and another increment call to get 67 and of course the macro result of 62.

This explains the bizarre output of this code. The combination of side effects, multiple evaluation inside a macro and reliance on order of parameter evaluation make this truly awful code. Worthy of an evil coding contest or a text book under the heading "Don't do this!"

There are three issues at play here.

First is that your increment function changes state every time you call it - it will always return a different value for each call. Second is that function arguments are not guaranteed to be evaluated left-to-right. Third is that after macro expansion, your printf call looks like this:

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

so it's possible for increment to be called 3 times.

Based on your output, increment is being called in this order:

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

That is, the expression (x) > (increment()) ? (x) : (increment()) (x) > (increment()) ? (x) : (increment()) is being evaluated first - increment() returns 47, which is not greater than x (50), so the result of the expression is 50.

Sometime after that, the lone increment() expression is called, which returns 52.

With the second printf call, we get

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

Again, (x) > (increment()) ? (x) : (increment()) (x) > (increment()) ? (x) : (increment()) is evaluated first. This time, increment() is called twice , returning 57 in the test condition, then 62 in the result.

It's then called a third time when the expression increment() is evaluated.

So...

The best way to deal with this is to assign the result of increment to a temporary, and use that temporary in the printf calls:

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

Most operators in C don't force left to right evaluation. The few that do are the logical && and || operators, the ?: ternary operator, and the , comma operator (which is not the same as the commas used in parameter lists for function calls).

A variable within a function, that is declared with static qualifier will be statically allocated and while it's scope will be limited to this function (I think), it will have a lifetime of a global. What this means is, it will retain it's value throughout function executions. It will only be initialized once.

Therefore upon every invocation of increment(), the function will return value 5 higher than the last time, starting with 42+5=47.

What's interesting is how does a macro affect your program execution. If MAX worked the way functions do, it would evaluate a and b expressions to integers or whatever other type it compares first. Then it would evaluate the a>b ? a : b expression. However since it's a preprocessor macro, all it does is text substitution. Resulting expression, hidden underneath confusing macro, is:

x > increment() ? x : increment()

Since increment() is not merely providing a value, but contains a side effect of changing it's static variable, it matters a lot whether it's evaluated once, or twice.

In this case upon reaching the second MAX macro, we compare x=50 with result of fourth invocation of increment(), ie. 62. As 50 < 62, we then evaluate the rightmost expression, fifth invocation of increment(), which returns 67, which is then returned by ?: operator to printf().

Note: a line above, in first printf(), in ?: underlying operator of MAX macro the leftmost operand evaluated to true, so only the middle operator was evaluated, which was "x". This is why first printf resulted in only two invocations of increment(), while the second printf() resulted in three such invocations.

edit: of course the remarks about this resulting in unspecified behavior are in the right. Nevertheless we empirically see that in practice your compiler produced execution in line with inuitive notion of expected order in which expression in function call gets executed.

compile the source code with gcc -c -S

and you get a .s file which is assembly language .

You can then see what is really happening in the .s file, and I suspect because #define MAX is a macro that when compiled it expands into inline code . I took C in college and it was years ago, this is the best answer i can give without scrutinizing the assembly language. For reasons such as this is why I avoid doing macros... because of how it can cause problems if you are not fully aware of how a compiler expands the macro into inline code. While a macro can save you some typing of source code and may look slick, it's pointless if code execution produces the wrong answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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