【问题标题】:When to use function-like macros in C何时在 C 中使用类似函数的宏
【发布时间】:2009-10-24 03:05:12
【问题描述】:

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

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

这让我想知道,为什么有人会选择实施 以这种方式使用类似函数的宏而不是实现 它是一个普通的普通 C 函数吗?有什么优点和 每个实现的缺点?

非常感谢!

【问题讨论】:

  • 随着散列函数的使用,这是非常微弱的 - 但是,它必须处理的数据似乎也非常简单。
  • 其实把这个做成macro也不是很亮眼。 inline static function 可能更合适。 C 自 1999 年以来就有内联,甚至在此之前就有 gcc。这个问题被引用为重复kfifo use of defines。如果 MS-Windows 是人们说这个宏合理的原因,那么它们之间有什么关系?

标签: c function c-preprocessor


【解决方案1】:

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

可能看起来不多。但在您的示例中,宏会变成 1-2 条机器语言指令,具体取决于您的 CPU:

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

而功能等价物将是更多的机器语言指令,通常类似于

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

更多的代码,嗯?如果您在 GUI 的窗口中渲染数万像素中的每一个像素,那么如果您使用宏,则运行速度会快得多。

就个人而言,我更喜欢使用 C++ 内联,因为它更具可读性且不易出错,但内联实际上也更多地是对编译器的提示,它不必采用。预处理器宏是编译器无法与之抗衡的一把大锤。

【讨论】:

  • 哇,你是对的,这就是将它实现为函数的更多代码!
  • 这就是内联函数的用途! :)
  • LiraNuna:内联函数不是大多数编译器支持的 ANSI C90 的一部分。它们仅在 C99 或 C++ 中
  • C99 现在当然已经十岁了。任何不支持它的 C 编译器都会严重过时。
  • @liw.fi:告诉微软!
【解决方案2】:

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

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

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

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

宏并不完美,但是关于“由于内联函数不再需要类似函数的宏”的流行格言纯属无稽之谈。为了完全替换类似函数的宏,C 将需要函数模板(如在 C++ 中)或至少需要函数重载(再次如在 C++ 中)。如果没有这种类似函数的宏,那么在 C 语言中,类似函数的宏仍然是非常有用的主流工具。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 很高兴知道。谢谢!只是好奇,您使用什么工具来优化您的 C 代码?我之前没有尝试过优化,不妨试一试。
    • 工具? Vim、gprof 和古老的 time 命令。 gprof 是一个非常好的分析器,开销很低,但你必须小心,因为它不跟踪宏(它看不到它们;宏的另一个缺点)。
    • 您使用的是哪个编译器版本,您要求它进行什么级别的优化?
    • gcc version 4.3.3 (Ubuntu 4.3.3-5ubuntu4) (g++)。优化为-O3
    【解决方案4】:

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

    宏也可用于一些常规 C 语言无法做到的方便的搜索和替换类型替换。

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

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

    【讨论】:

    • 您能否举例说明这些只能使用您所说的宏完成的搜索和替换替换?
    • 代码生成行如:#define COLORVAL(color,val) int color##_number = val;我自己不会做太多,但如果您需要红色、蓝色、绿色、...、黑色的线条,那么它会有所帮助。
    • “Unix Hater 手册”的第 212 页(.pdf simson.net/ref/ugh.pdf 中的第 248 页,来自 cs.washington.edu/homes/weise/uhh-download.html)讨论了一些相关的困难。
    【解决方案5】:

    你的例子根本不是一个函数,

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

    我认为,这只是一种编写更具可读性的代码的方法,将强制转换和 mod 操作封装到单个宏“HASH(fp)”中


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

    int hashThis(int fp) { 返回 ((fp)%NHASH); }

    对于一个函数来说,这是一个过大的杀伤力,

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

    【讨论】:

      【解决方案6】:

      C Preprocessor 可用于创建内联函数。在您的示例中,代码将显示为调用函数 HASH,但实际上只是内联代码。

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

      【讨论】:

        猜你喜欢
        • 2020-01-09
        • 2014-08-27
        • 2022-11-24
        • 1970-01-01
        • 2020-07-15
        • 1970-01-01
        • 1970-01-01
        • 2015-10-29
        • 2021-12-23
        相关资源
        最近更新 更多