C++ #定义SQR(x)x*x。意外的回答

C++ #定义SQR(x)x*x。意外的回答,c++,macros,C++,Macros,为什么这个宏的输出是144而不是121 #include<iostream> #define SQR(x) x*x int main() { int p=10; std::cout<<SQR(++p); } #包括 #定义SQR(x)x*x int main() { int p=10; std::cout因为您使用的是未定义的行为。当宏展开时,您的代码变成: std::cout<<++p*++p; std::cout这是预处理器宏的一个陷

为什么这个宏的输出是144而不是121

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

int main()
{
    int p=10;
    std::cout<<SQR(++p);
}
#包括
#定义SQR(x)x*x
int main()
{
int p=10;

std::cout因为您使用的是未定义的行为。当宏展开时,您的代码变成:

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

std::cout这是预处理器宏的一个陷阱。问题是表达式
++p
被使用了两次,因为预处理器只是将宏“call”替换为几乎一字不差的主体

因此,编译器在宏扩展后看到的是

std::cout<<++p*++p;
这有两件事:第一件事是表达式
++p
将只计算一次。另一件事是,现在除了
int
值之外,您不能向函数传递任何值。使用预处理器宏,您可以像
SQR(“A”)一样“调用”它
,而当您(有时)从编译器中得到加密错误时,预处理器不会在意


此外,被标记为
inline
,编译器可能会完全跳过实际的函数调用,并将(正确的)
x*x
直接放在调用的位置,从而使其与宏扩展一样“优化”。

因为SQR(++p)扩展到++p*++p,它在C/C++中具有未定义的行为。在本例中,它在计算乘法之前将p增加了两次。但您不能依赖于此。使用不同的C/C++编译器可能是121(甚至42)。

使用此宏进行平方运算的方法有两个问题:

首先,对于参数
++p
,增量操作执行两次。这当然不是故意的。(作为一般经验法则,只是不要在“一行”中做几件事。将它们分成更多的语句。)。它甚至不会在两次递增时停止:这些递增的顺序没有定义,因此此操作的结果没有保证

第二,即使你没有<代码> ++P<代码>作为参数,你的宏仍然有一个bug!考虑输入<代码> 1 + 1 < /代码>。预期输出是“代码> 4”/代码>。<代码> 1 + 1 < /代码>没有副作用,所以它应该是好的,不是吗?因为<代码> SQR(1 +1)

转换为
1+1*1+1
,其计算结果为
3

要至少部分修复此宏,请使用括号:

#define SQR(x) (x) * (x)
总之,您应该简单地用函数替换它(以添加类型安全性!)

你可以考虑把它做成一个模板

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

如果你想学习C++,为什么不使用内联函数呢?你可以看到,宏会导致评估问题……Bjarne Stroustrups。它解释了使用宏的错误,并给出了你遇到的同样的例子。更重要的是,它在C++中有未定义的行为(关于这个问题)。还有。@Angew:当然可以。谢谢。我已经在我的文本中将C改为C/C++了。你可以将
'A'
或任何其他数值传递给这个函数(假设你想要的参数类型是
int
);更糟糕的是,如果你传递了一个浮点值,它会悄悄地给出一个意外的结果。模板可能更合适,尤其是作为宏的替代品。@MikeSeymour当然你是对的,但我认为模板目前可能有点过头了。你对此有参考吗?这是如何/为什么未定义的?Thanks。
inline int sqr(const int x)
{
    return x * x;
}
#define SQR(x) (x) * (x)
int sqr(int x)
{
    return x * x;
}
template <typename Type>
Type sqr(Type x)
{
   return x * x; // will only work on types for which there is the * operator.
}
constexpr int sqr(int x)
{
    return x * x;
}