What is double evaluation and why should it be avo

2020-02-03 09:33发布

问题:

I was reading that in C++ using macros like

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

can result in a 'double evaluation'. Can someone give me an example of when a double evaluation occurs and why it's bad?

P.S.: Surprisingly I couldn't find any detailed explanation when googling for it except for an example in Clojure (which I can't understand).

回答1:

Imagine you wrote this:

#define Max(a,b) (a < b ? b : a)

int x(){ turnLeft();   return 0; }
int y(){ turnRight();  return 1; }

then called it like this:

auto var = Max(x(), y());

Do you know that turnRight() will be executed twice? That macro, Max will expand to:

auto var = (x() < y() ? y() : x());

After evaluating the condition x() < y(), the program then takes the required branch between y() : x(): in our case true, which calls y() for the second time. See it Live On Coliru.

Simply put, passing an expression as an argument to your function-like macro, Max will potentially evaluate that expression twice, because the expression will be repeated where ever the macro parameter it takes on, is used in the macro's definition. Remember, macros are handled by the preprocessor.


So, bottom line is, do not use macros to define a function (actually an expression in this case) simply because you want it to be generic, while it can be effectively done using a function templates

PS: C++ has a std::max template function.



回答2:

a and b occur two times in the macro definition. So if you use it with arguments that have side-effects, the side-effects are executed two times.

max(++i, 4);

will return 6 if i = 4 before the call. As it is not the expected behavior, you should prefer inline functions to replace such macros like max.



回答3:

Consider the following expression:

 x = max(Foo(), Bar());

Where Foo and Bar are like this:

int Foo()
{
    // do some complicated code that takes a long time
    return result;
}

int Bar()
{
   global_var++;
   return global_var;
}

Then in the original max expression is expanded like:

 Foo() > Bar() ? Foo() : Bar();

In either case, Foo or Bar is going to executed twice. Thereby taking longer than necessary or changing the program state more than the expected number of times. In my simple Bar example, it doesn't return the same value consistently.



回答4:

The macro language in C and C++ is processed by a dedicated parser at the 'pre-processing' stage; the tokens are translated and the output is then fed into the input stream of the parser proper. #define and #include tokens are not recognized by the C or C++ parsers themselves.

This is important because it means that when a macro is said to be "expanded" it means literally that. Given

#define MAX(A, B) (A > B ? A : B)

int i = 1, j = 2;
MAX(i, j);

what the C++ parser sees is

(i > j ? i : j);

However if we use the macro with something more complex, the same expansion happens:

MAX(i++, ++j);

is expanded to

(i++ > ++j ? i++ : ++j);

If we pass something that makes a function call:

MAX(f(), g());

this will expand to

(f() > g() ? f() : g());

If the compiler/optimizer can demonstrate that f() has no side-effects, then it will treat this as

auto fret = f();
auto gret = g();
(fret > gret) ? fret : gret;

If it can't, then it will have to call f() and g() twice, for example:

#include <iostream>

int f() { std::cout << "f()\n"; return 1; }
int g() { std::cout << "g()\n"; return 2; }

#define MAX(A, B) (A > B ? A : B)

int main() {
    MAX(f(), g());
}

Live demo: http://ideone.com/3JBAmF

Similarly if we were calling an extern function, the optimizer may not be able to avoid calling the function twice.