2009-10-28 110 views
11

在以前question我认为是一个很好的答案被否决的建议使用宏什么时候应该使用宏而不是内联函数?

#define radian2degree(a) (a * 57.295779513082) 
#define degree2radian(a) (a * 0.017453292519) 

,而不是内联函数。请原谅这个新手问题,但在这种情况下宏是如此邪恶?

+9

在这种情况下,degree2radian(45 + a/2.0)不符合您的想法。总是将扩张中的每一个论点都加上括号 - 好吧,几乎总是;主要的例外情况是当你做令牌粘贴或字符串化时。 – 2009-10-28 22:01:20

+3

Jonathan说 - 有一件事情比看到一个宏定义更糟,因为所有该死的parens都是一个痛苦的屁股阅读,正在遇到一个没有所有那些该死的parens的宏定义。 – 2009-10-29 00:11:13

+3

还有宏的ALLCAPS约定,它至少将它们放入它们自己的伪名称空间中。 – 2009-10-29 13:42:57

回答

6

有几个关于宏的严重的坏事。

他们是文字处理,并没有作用域。如果您使用#define foo 1,则任何后续使用foo作为标识符都将失败。这可能导致奇怪的编译错误和难以找到的运行时错误。

他们没有正常意义上的争论。您可以编写一个函数,该函数将取两个值并返回最大值,因为参数将被计算一次,之后将使用该值。你不能写一个宏来做这件事,因为它会至少评估一个参数两次,并且会失败,例如max(x++, --y)

还有一些常见的缺陷。他们很难得到多个陈述,而且他们需要很多可能多余的括号。

在你的情况,你需要括号:

#define radian2degree(a) (a * 57.295779513082) 

需求是

#define radian2degree(a) ((a) * 57.295779513082) 

和你仍然对任何人谁在某些范围内写一个函数radian2degree加强,相信那定义将在其自己的范围内工作。

+1

我总是发现“特征”x“是邪恶永远不会使用它”的陈述似乎暗示程序员是非常愚蠢的。建议使用RADIAN2DEGREEmac或类似的方式将宏标识为宏。 – 2009-10-29 10:47:00

+5

“特征X是邪恶的”并不意味着“不要使用特征X”,并且我不太认为宏是邪恶的。全帽大会是有价值的,但并不能解决所有问题。严格的括号和其他技巧一样。如果可能的话,我建议避免使用类似功能的宏,因为通常有更好的方法来做任何你想做的事情。 – 2009-10-29 13:39:24

+1

@David很好的答案,但它没有回答这个问题:“什么时候应该使用宏而不是内联函数?”看起来你的回答正好相反:为什么你不应该使用宏。但是什么时候使用宏而不是函数“更好”呢? – styfle 2011-09-01 16:45:43

1

如果可能,请始终使用内联函数。这些都是类型安全的,不容易重新定义。

定义可以被定义为未定义,并且没有类型检查。

+1

C(而不是C++)的缺点是并非所有编译器都必须支持它们,或者默认支持它们的模式。它们是在C99中添加的,但是一些编译器(例如MSVC)不符合C99标准。我不知道MSVC是否支持这一标准 - 这可能是因为它也支持C++支持。然而,它没有其他的C99功能,这将是非常好的。 – 2009-10-28 22:04:52

+2

一个智能编译器会忽略'inline'指令,并且可以忽略单个调用指示的内联指令。 – 2009-10-28 22:08:51

+0

@David +1,内联是一个纯粹的建议。 – LB40 2009-10-28 22:56:19

7

对于这个特定的宏,如果我使用它,如下所示:

int x=1; 
x = radian2degree(x); 
float y=1; 
y = radian2degree(y); 

就没有类型检查,以及X,Y将包含不同的值。

此外,下面的代码

float x=1, y=2; 
float z = radian2degree(x+y); 

不会做你的想法,因为这将转化为

float z = x+y*0.017453292519; 

,而不是

float z = (x+y)+0.017453292519; 

这是预期的结果。

这些只是misbehavior和误用宏可能有的几个例子。

编辑

你可以看到这个here

+3

很好的答案。还值得一提的是名称空间污染(例如,我不能再声明名为'radian2degree'的局部变量或结构字段)。 – 2009-10-28 21:56:59

+0

我不明白你的第一个投诉。 x不会被宏转换为浮点,而是被乘法转换为浮点。 – 2009-10-28 22:10:04

+1

我的观点是这两个用途都是有效的,并将编译,但你不会看到任何迹象。你的宏_should_只接受浮点数,但它会接受任何东西。 – Amirshk 2009-10-28 22:15:59

0

宏附加讨论是邪恶的,因为你可能最终通过比一个变量或一个标量给它更多,这可能在不必要的行为解析(定义一个最大宏以确定a和b之间的最大值,但将一个++和b ++传递给宏并查看会发生什么)。

0

如果您的函数将被内联,函数和宏之间没有性能差异。但是,函数和宏之间存在若干可用性差异,所有这些都有利于使用函数。

如果您正确构建宏,则没有问题。但是,如果你使用一个函数,编译器会为你每次都做正确的。所以使用函数会使编写错误代码变得更加困难。

2

宏常常被滥用,并且可以使用它们很容易地犯错误,如您的示例所示。就拿表达radian2degree(1 + 1):

  • 与宏将扩大到1 + 1 * 57.29 = ... ... 58.29
  • 用这将是一个功能你想让它即,(1 + 1)* 57.29 ... = ...

更一般地,宏是邪恶的,因为他们样子功能所以他们会诱骗您使用它们就象函数,但他们有自己的微妙的规则。在这种情况下,正确的方法是写这将是(注意周围的paranthesis):

#define radian2degree(a) ((a) * 57.295779513082) 

但你应该坚持内联函数。请参阅从C++ FAQ精简版这些链接,其微妙之处邪恶宏的更多示例和:

+1

+1,但a)宏不是“一般的邪恶”。他们可能会被滥用,但他们并不是所有邪恶的根源,不仅仅是“goto”。 b)为什么链接到一个C问题的C++ FAQ?没有C++标记,并且在问题中没有特定于C++的信息。 – 2009-10-29 00:13:42

+0

你说的“一般的邪恶”是正确的,它太苛刻,一般。我编辑它。 与b)相关,据我看到我给出的C++ FAQ链接中的所有信息也适用于C,我认为宏的问题/微妙在这里被清楚地解释,所以我将它们看作是这个问题的有价值的链接。那么为什么不链接到C++ FAQ? – 2009-10-29 08:36:27

1

编译器的预处理器是finnicky事,因此是聪明的招数的可怕候选人。正如其他人指出的那样,编译器很容易误解用户对宏的意图,并且很容易误解宏的实际功能,但最重要的是,您无法在调试器中进入宏!

8

大多数其他答案都讨论了为什么宏是邪恶的,包括您的示例如何具有常见的宏使用缺陷。这里是Stroustrup的:http://www.research.att.com/~bs/bs_faq2.html#macro

但是你的问题是问什么宏仍然是好的。有一些事情,其中​​宏比内联函数更好,而这也正是你正在做的事情,根本无法与内联函数来实现,如:

  • 标记粘贴
  • 处理行号或这样的(如在assert()产生错误消息)
  • 用的东西并不表达式(例如,使用一个类型名称的offsetof()使用许多实现如何创建一个转换操作)
  • 宏处理得到的计数数组元素(不能用一个函数来做,因为数组名也会衰减到一个指针e asily)
  • 创建“类型多态”功能-C一样东西的地方模板不适

但与具有内联函数语言,宏的更常见的用途不应该是必要的。当我处理不支持内联函数的C编译器时,我甚至不愿意使用宏。我尽量不使用它们来创建类型无关的函数(如果可能的话)(用类型指示符作为名称的一部分来创建几个函数)。

我也转向使用枚举命名数字常量而不是#define

+0

宏的另一个有用的例子是初始化数据段中的对象: int global = radian2degree(2 * PI); – calandoa 2009-10-29 18:32:22

+0

是的 - 我自己很少处理浮点数,所以我现在通常使用枚举而不是宏来命名数字常量。但很显然,像“PI”这样的浮动工具无法做到,所以它是一个宏。 – 2009-10-29 19:22:42

+0

在带有gcc扩展的编译器上,与内联函数不同,宏可以测试它们的参数是否可以评估为常量。如果希望有一个像“reverse8bits(x)”这样的计算位反转字节值的“函数”,最好使用(((x&128)>> 7)|((x&64)>> 5) ...)''当'x'是一个常量时(在这种情况下,看起来不愉快的表达式会产生一个常量),但当'x'是一个变量时调用一个库例程。我希望'C'允许内联函数根据参数是否为编译时常量来重载,因为这样会更干净,并且...... – supercat 2012-10-12 16:50:49

相关问题