使用宏进行小操作,这是一个好的做法吗?

3

我有一小段代码需要读取32位整数中打包的4位值。由于我需要多次调用此操作,即使它很简单,我也需要最大速度。

我曾考虑过使用宏和内联函数,因此我制作了这个宏:

#define UI32TO4(x, p) (x >> ((p - 1) *4) & 15)

而且我有一个内联函数也能做同样的事情。

static inline Uint8 foo_getval(Uint32 bits, int pos){
    return (bits >> ((pos-1)*4)) & 15;
}

考虑到操作的简便性,以及这个调用已经准备好了值(因此不可能调用错误的类型或传递过大的值等等),哪种方式最好使用?或者至少对于后来读取/修改代码的其他人来说最容易理解呢?

编辑!忘记提到,我正在使用C99。


我不确定C语言中的内联函数,但我认为inline关键字只是一个建议。宏始终是“内联”的。另一方面,使用函数允许编译器进行更好的类型检查。 - Some programmer dude
在替换时,你应该(几乎)总是用括号将宏参数括起来:#define UI32TO4(x, p) ((x) >> (((p) - 1) * 4 & 15)。例外情况是当参数作为函数的参数传递时(例如 #define FUNC(x, y) otherfunc(x, y));当参数是表达式的一部分时,需要用括号括起来。 - Jonathan Leffler
1
相信你的编译器:使用内联函数。 - Cody Gray
1
@Delirium_4:想象一下这样的调用:SOME_MACRO(a+b,c) - 如果你在宏参数周围没有括号,宏代码的评估可能不会产生你所期望的结果。 - Mat
实际上我先写了宏,但后来按照我使用的其他函数的风格重写成了函数。你可以说宏是原型。 - Delirium_4
显示剩余2条评论
3个回答

8

这个函数更加安全。你假设值总是“正确”的,只有在开发代码时才是如此。你无法确定在后续过程中是否会传递意外的值(或者在疲劳时自己传递了意外的值)。

编译器将在其认为是有效的情况下进行内联处理。尽可能使用类型安全的函数,仅在没有其他实际选择时使用宏。


啊,我担心在我完成后谁会想要重用这段代码。我明白了,那我就使用内联函数吧。 - Delirium_4

4
我会使用内联函数,因为宏可能会导致不必要的副作用。仅在必要时使用宏以节省打字时间。
如果宏名称与另一个编译单元中的函数名称相同,则会出现奇怪的编译错误。这些问题可能很难找到,特别是如果宏在其他地方扩展并且没有发生错误。
此外,函数会警告你参数类型,并且不会让你将double类型赋给pos。而宏可以允许这种情况发生。

2

现在时间已经很晚了,我心情有些糟(而且我可能会删除这篇文章),但是我已经厌倦了反对宏的相同论点一遍又一遍地被重复提及(两次冗余):

  • Joachim Pileborg(如上所述)说“使用函数可以让编译器进行更好的类型检查”。这种说法经常被人提到,但我不相信。使用宏时,编译器已经拥有所有可用的类型信息。而函数只会破坏这些信息(并有可能通过将寄存器推入堆栈来破坏优化,但那是一个次要问题)。

  • frast(如上所述)说“宏可能会导致意外的副作用”。是真的--函数也可能会有这个问题。我认为规则是始终使用大写字母表示没有函数语义的宏。这个规则经常被打破。但这里不适用:OP已经多余地使用了大写字母和函数语义

但我建议稍微改进一下。OP已经正确地将整个宏放在括号中,但每个参数也应该用括号括起来:

   #define UI32TO4(x, p) ((x) >> (((p) - 1) * 4) & 15)

始终将宏参数用括号括起来,除非您正在进行字符串或标记连接等操作。

宏当然是危险的,但函数也是如此。 (而STL 最好不要提)。


1
哦,你可能会迟到而且心情不好,但这仍然是一个非常有趣的答案。拥有双方观点的论据是很好的。我只是不太清楚你所说的使用大写字母来表示不需要宏定义的宏的意思是什么......这是否意味着我应该使用小写字母来编写宏函数?(非常抱歉,我不是英语母语者,有时会迷失在细节中......你甚至可能没有那个意思) - Delirium_4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接