#define SQR(x) x*x. Unexpected Answer

2019-01-20 20:37发布

问题:

Why this macro gives output 144, instead of 121?

#include<iostream>
#define SQR(x) x*x

int main()
{
    int p=10;
    std::cout<<SQR(++p);
}

回答1:

The approach of squaring with this macro has two problems:

First, for the argument ++p, the increment operation is performed twice. That's certainly not intended. (As a general rule of thumb, just don't do several things in "one line". Separate them into more statements.). It doesn't even stop at incrementing twice: The order of these increments isn't defined, so there is no guaranteed outcome of this operation!

Second, even if you don't have ++p as the argument, there is still a bug in your macro! Consider the input 1 + 1. Expected output is 4. 1+1 has no side-effect, so it should be fine, shouldn't it? No, because SQR(1 + 1) translates to 1 + 1 * 1 + 1 which evaluates to 3.

To at least partially fix this macro, use parentheses:

#define SQR(x) (x) * (x)

Altogether, you should simply replace it by a function (to add type-safety!)

int sqr(int x)
{
    return x * x;
}

You can think of making it a template

template <typename Type>
Type sqr(Type x)
{
   return x * x; // will only work on types for which there is the * operator.
}

and you may add a constexpr (C++11), which is useful if you ever plan on using a square in a template:

constexpr int sqr(int x)
{
    return x * x;
}


回答2:

This is a pitfall of preprocessor macros. The problem is that the expression ++p is used twice, because the preprocessor simply replaces the macro "call" with the body pretty much verbatim.

So what the compiler sees after the macro expansion is

std::cout<<++p*++p;

Depending on the macro, you can also get problems with operator precedence if you are not careful with placing parentheses where needed.

Take for example a macro such as

// Macro to shift `a` by `b` bits
#define SHIFT(a, b)  a << b

...

std::cout << SHIFT(1, 4);

This would result in the code

std::cout << 1 << 4;

which may not be what was wanted or expected.


If you want to avoid this, then use inline functions instead:

inline int sqr(const int x)
{
    return x * x;
}

This has two things going for it: The first is that the expression ++p will only be evaluated once. The other thing is that now you can't pass anything other than int values to the function. With the preprocessor macro you could "call" it like SQR("A") and the preprocessor would not care while you instead get (sometimes) cryptical errors from the compiler.

Also, being marked inline, the compiler may skip the actual function call completely and put the (correct) x*x directly in the place of the call, thereby making it as "optimized" as the macro expansion.



回答3:

Because you are using undefined behaviour. When the macro is expanded, your code turns into this:

std::cout<<++p*++p;

using increment/decrement operators on the same variable multiple times in the same statement is undefined behaviour.



回答4:

Because SQR(++p) expands to ++p*++p, which has undefined behavior in C/C++. In this case it incremented p twice before evaluating the multiplication. But you can't rely on that. It might be 121 (or even 42) with a different C/C++ compiler.



标签: c++ macros