内联函数与预处理器宏

Sub*_*odh 107 c c++ macros inline

内联函数与预处理器宏有何不同?

LBu*_*kin 120

预处理器宏只是应用于代码的替换模式.它们几乎可以在代码中的任何位置使用,因为在任何编译开始之前它们都会被扩展替换.

内联函数是实体函数,其主体直接注入其调用站点.它们只能在适合函数调用的地方使用.

现在,就类似函数的上下文中使用宏与内联函数而言,请注意:

  • 宏不是类型安全的,无论它们是否在语法上是正确的,都可以扩展 - 编译阶段将报告宏扩展问题导致的错误.
  • 宏可以在您不期望的环境中使用,从而导致问题
  • 宏更灵活,因为它们可以扩展其他宏 - 而内联函数不一定会这样做.
  • 宏会因为扩展而导致副作用,因为输入表达式会复制到模式中出现的任何位置.
  • 内联函数并不总是保证内联 - 一些编译器只在发布版本中执行此操作,或者在专门配置时执行此操作.此外,在某些情况下,内联可能是不可能的.
  • 内联函数可以为变量(特别是静态函数)提供范围,预处理器宏只能在代码块{...}中执行此操作,而静态变量的行为方式不会完全相同.

  • 内联函数并不总是保证内联:因为编译器不会内联如果这样做会生成更慢的代码等.编译器做了很多分析,工程师不能做正确的事情. (35认同)
  • 我相信递归函数是大多数编译器忽略内联的另一个例子. (13认同)
  • 未提及的一点是内联可能受编译标志的影响.例如,当您构建最大速度(如GCC -O2/-O3)时,编译器将选择内联许多函数,但是当您构建最小大小(-Os)时,如果通常内联函数只调用一次(或非常小的函数) ).使用宏没有这样的选择. (7认同)

Thi*_*hib 70

首先,预处理器宏只是编译前代码中的"复制粘贴".所以没有类型检查,可能会出现一些副作用

例如,如果要比较2个值:

#define max(a,b) ((a<b)?b:a)
Run Code Online (Sandbox Code Playgroud)

如果您使用max(a++,b++)例如(ab将增加两次),则会出现副作用.相反,使用(例如)

inline int max( int a, int b) { return ((a<b)?b:a); }
Run Code Online (Sandbox Code Playgroud)

  • 只是想添加到您的示例中,除了副作用之外,宏还可能带来额外的工作量,请考虑“ max(fibonacci(100),factorial(10000))`”,较大的将被计算两次:( (3认同)
  • 在宏中,为什么`a`或`b`会递增两次?是因为 `max(a++, b++)` 会被替换为 `(a++ &lt; b++) ?b++ : a++`,对吗?我想这也回答了我的上述问题(关于@watashiSHUN 的评论)。提前致谢! (2认同)

Dat*_*her 14

内联函数由编译器扩展,其中宏由预处理器扩展,这仅仅是文本替换.Hence

  • 在宏调用期间没有类型检查,而在函数调用期间完成类型检查.

  • 由于重新评估参数和操作顺序,在宏扩展期间可能会出现不期望的结果和低效率.例如

    #define MAX(a,b) ((a)>(b) ? (a) : (b))
    int i = 5, j = MAX(i++, 0);
    
    Run Code Online (Sandbox Code Playgroud)

    会导致

    int i = 5, j = ((i++)>(0) ? (i++) : (0));
    
    Run Code Online (Sandbox Code Playgroud)
  • 宏扩展之前不会评估宏参数

    #define MUL(a, b) a*b
    int main()
    {
      // The macro is expended as 2 + 3 * 3 + 5, not as 5*8
      printf("%d", MUL(2+3, 3+5));
     return 0;
    }
    // Output: 16`
    
    Run Code Online (Sandbox Code Playgroud)
  • 无法在宏中使用return关键字来返回值,就像函数一样.

  • 内联函数可以重载

  • 传递给宏的标记可以使用运算符##连接,称为Token-Pasting运算符.

  • 宏通常用于代码重用,其中内联函数用于消除函数调用期间的时间开销(超时)(避免跳转到子例程).


sha*_*oth 12

关键的区别是类型检查.编译器将检查您作为输入值传递的是否是可以传递给函数的类型.对于预处理器宏来说情况并非如此 - 它们在任何类型检查之前都会被扩展,这会导致严重且难以检测到的错误.

以下是其他几个不太明显的要点.


Ric*_*dle 11

要为已经给出的内容添加另一个区别:您无法#define在调试器中单步调试,但可以单步执行内联函数.


Kir*_*sky 8

宏忽略了名称空间.这使他们变得邪恶.