宏C / C ++的奇怪行为

时间:2013-06-24 23:05:58

标签: c++ gcc macros c-preprocessor pi

我正在使用一些宏,并观察到一些奇怪的行为。

我已将PI定义为常量,然后在宏中将其用于将度数转换为弧度,将弧度转换为度数。度数弧度工作正常,但弧度到度数不会:

piTest.cpp:

#include <cmath>
#include <iostream>
using namespace std;

#define PI atan(1) * 4
#define radians(deg)  deg * PI / 180
#define degrees(rad)  rad * 180 / PI

int main()
{
  cout << "pi: " << PI << endl;
  cout << "PI, in degrees: " << degrees(PI) << endl;
  cout << "45 degrees, in rad: " << radians(45) << endl;
  cout << "PI * 180 / PI: " << (PI * 180 / PI) << endl;
  cout << "3.14159 * 180 / 3.14159: " << (3.14159 * 180 / 3.14159) << endl;
  cout << "PI * 180 / 3.14159: " << (PI * 180 / 3.14159) << endl;
  cout << "3.14159 * 180 / PI: " << (3.14159 * 180 / PI) << endl;

  return 0;

}

当我编译并运行时,我得到以下输出:

pi: 3.14159
PI, in degrees: 2880
45 degrees, in rad: 0.785398
PI * 180 / PI: 2880
3.14159 * 180 / 3.14159: 180
PI * 180 / 3.14159: 180
3.14159 * 180 / PI: 2880

似乎我的常数PI在分子中起作用,但不是分母。我在C中观察到了相同的行为。我正在运行gcc版本4.6.3

任何人都可以解释为什么我会出现这种行为吗?

5 个答案:

答案 0 :(得分:9)

宏是(相对简单的)文本替换。

在定义中使用括号(包括宏本身和宏参数):

#define PI (atan(1) * 4)
#define radians(deg)  ((deg) * PI / 180)
#define degrees(rad)  ((rad) * 180 / PI)

答案 1 :(得分:5)

首先,cmath定义M_PI,使用它。

其次,cpp宏进行文本替换。这意味着:

#define PI atan(1) * 4
a = 1 / PI;

将变成这个:

a = 1 / atan(1) * 4;

在c / c ++编译器有机会看到你的代码之前,它会将它视为等同于:

a = (1 / atan(1)) * 4;

这不是你想要的。

您的定义应如下所示:

#define PI (atan(1) * 4)

一切都应该没问题。

这并不是一种奇怪的行为,但是c-preprocessor的行为记录良好。

您应该在网上搜索有关宏的其他陷阱。 (提示:参数传递)

答案 2 :(得分:2)

您应该使用宏的括号来指定优先级。除此之外,我认为在很多情况下math.h会为你定义PI

答案 3 :(得分:2)

宏只是在不考虑上下文的​​情况下进行文本替换,所以你最终得到的是:

cout << "PI, in degrees: " << atan(1) * 4 * 180 / atan(1) * 4 << endl;

请注意第二个atan(1) * 4周围明显缺少parens,导致它仅除以atan(1),然后乘以4.

相反,使用内联函数和全局变量:

const double PI = atan(1) * 4;
double radians(double deg) { return deg * PI / 180; }
double degrees(double rad) { return rad * 180 / PI; }

答案 4 :(得分:1)

同样好的做法:在所有参数周围添加括号:

#define radians(deg)  ((deg) * PI / 180)

因为您作为参数传递的表达式也可能包含运算符。

甚至更好:使用(内联)函数而不是宏,以避免在多次评估参数时出现副作用的意外情况,如下所示:

#define sqr(x)  ((x) * (x))

内联函数的唯一缺点是:您只能为一种类型定义它们(除非您使用C ++模板)