何时在C中使用类似函数的宏

今晚我正在阅读一些用C语言编写的代码,文件顶部是类似函数的宏HASH:

#define HASH(fp) (((unsigned long)fp)%NHASH) 

这让我想知道,为什么有人会选择使用类似函数的宏来实现这种函数,而不是将它作为常规的vanilla C函数实现? 每种实施的优缺点是什么?

谢谢你!

这样的宏可以避免函数调用的开销。

看起来似乎并不多。 但在您的示例中,宏将变为1-2个机器语言指令,具体取决于您的CPU:

  • 获取fp的内存值并将其放入寄存器中
  • 取寄存器中的值,按固定值进行模数(%)计算,并将其保留在同一寄存器中

而function相当的将是更多的机器语言指令,通常是类似的

  • 将fp的值粘贴在堆栈上
  • 调用该函数,该函数还将下一个(返回)地址放在堆栈上
  • 也许在函数内部构建一个堆栈框架,具体取决于CPU架构和ABI约定
  • 从栈中获取fp的值并将其放入寄存器中
  • 取寄存器中的值,按固定值进行模数(%)计算,并将其保留在同一寄存器中
  • 也许从寄存器中获取值并将其放回堆栈,具体取决于CPU和ABI
  • 如果构建了堆栈框架,请将其展开
  • 将返回地址弹出堆栈并继续执行指令

还有更多代码,嗯? 如果你正在做一些事情,比如在GUI中的窗口中渲染成千上万个像素中的每一个像素,那么如果使用宏,事情就会快得多。

就个人而言,我更喜欢使用C ++内联作为更具可读性和更不容易出错的内容,但内联也更多地是对编译器的暗示,它不必采取。 预处理器宏是编译器无法争辩的大锤。

基于宏的实现的一个重要优点是它不依赖于任何具体的参数类型。 C语言中的函数式宏在很多方面都是C ++中的模板函数(C ++中的模板诞生为“更文明”的宏,BTW)。 在这种特殊情况下,宏的参数没有具体类型。 它可能绝对是可转换为unsigned long类型的任何东西。 例如,如果用户如此满意(并且如果他们愿意接受实现定义的后果),他们可以将指针类型传递给此宏。

无论如何,我不得不承认这个宏并不是宏的类型无关灵活性的最好例子,但总的来说,灵活性很常见。 同样,当某个function由某个function实现时,它仅限于特定的参数类型。 在许多情况下,为了将类似的操作应用于不同的类型,有必要提供具有不同类型的参数的几个函数(以及不同的名称,因为这是C),而同样可以通过仅一个类似函数的宏来完成。 例如,宏

 #define ABS(x) ((x) >= 0 ? (x) : -(x)) 

适用于所有算术类型,而基于function的实现必须提供相当多的算法类型(我暗示标准的abslabsllabsfabs )。 (是的,我知道传统上提到的这种宏的危险。)

宏并不完美,但关于“由于内联函数而不再需要类似函数的宏”的流行格言只是毫无意义。 为了完全替换类似函数的宏,C将需要函数模板(如在C ++中)或至少需要函数重载(如在C ++中那样)。 如果没有那个类似function的宏,并且仍将是C中非常有用的主流工具。

一方面,宏是坏的,因为它们是由预处理器完成的,它不了解语言并进行文本替换。 他们通常有很多限制。 我看不到上面的一个,但通常宏是难看的解决方案。

另一方面,它们有时甚至比static inline方法更快。 我正在大力优化一个简短的程序,发现与宏相比,调用static inline方法需要大约两倍的时间(只是开销,而不是实际的函数体)。

人们为使用宏(在“普通旧C”中)给出的最常见(也是最常见的错误)原因是效率论证。 如果您实际编写了代码并优化了真正的瓶颈(或正在编写一个可能有一天可能成为某个人的瓶颈的库函数),那么使用它们来提高效率是很好的。 但是大多数坚持使用它们的人实际上并没有对任何东西进行分析,只是在没有任何好处的情况下制造混乱。

宏也可以用于常规C语言不具备的一些方便的搜索和替换类型替换。

我在维护宏滥用者编写的代码时遇到的一些问题是,宏看起来很像函数,但没有出现在符号表中,所以试图将它们追溯到它们在庞大的代码集中的起源可能会非常烦人(其中这个东西定义了吗?!)。 在ALL CAPS中编写宏显然对未来的读者有所帮助。

如果它们不仅仅是相当简单的替换,如果您必须使用调试器逐步跟踪它们,它们也会产生一些混淆。

你的例子根本不是一个function,

 #define HASH(fp)(((unsigned long)fp)%NHASH)
 //这是演员^^^^^^^^^^^^^^^
 //这是你的价值'fp'^^
 //这是MOD操作^^^^^^

我认为,这只是一种编写更具可读性的代码的方式,其中将转换和mod操作包装成单个宏’ HASH(fp)


现在,如果您决定为此编写一个函数,它可能看起来像,

 int hashThis(int fp)
 {
   return((fp)%NHASH);
 }

对于一个函数来说相当难以理解,

  • 介绍一个呼叫点
  • 介绍了调用堆栈的设置和恢复

C预处理器可用于创建内联函数。 在您的示例中,代码似乎调用函数HASH,而只是内联代码。

当C ++引入内联函数时,消除了执行宏函数的好处。 许多较旧的API(如MFC和ATL)仍然使用宏函数来执行预处理器技巧,但它只会让代码变得复杂且难以阅读。