【问题标题】:Inline functions vs Preprocessor macros内联函数与预处理器宏
【发布时间】:2010-11-11 09:00:40
【问题描述】:

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

【问题讨论】:

标签: c++ c macros inline


【解决方案1】:

要了解宏和内联函数的区别,首先我们应该知道它们到底是什么以及什么时候应该使用它们。

功能

int Square(int x){
return(x*X);
}
int main()
{
int value = 5;
int result = Square(value);
cout << result << endl;
}
  • 函数调用有与之相关的开销,因为函数完成执行后,它必须知道它必须返回的位置,并且还需要将值存储在堆栈内存中。

  • 对于小型应用程序来说这不是问题,但是让我们以每秒发生数千笔交易的金融应用程序为例,我们不能使用函数调用。

宏:

# define Square(x) x*x;
int main()
{
int value = 5;
int result = Square(value);
cout << result << endl;
}
  • 宏在预处理阶段起作用,即在这个阶段用#关键字编写的语句将被替换为内容,即

int 结果 = Square(x*x)

但是宏有与之相关的错误。

#define Square(x) x*x
int main() {
    int val = 5;
    int result = Square(val + 1);
    cout << result << endl;
    return 0;
}

这里的输出是 11 而不是 36

内联函数

inline int Square(int x) {
    return x * x;
}

int main() {
    using namespace std;
    int val = 5;
    int result = Square(val + 1);
    cout << result << endl;
    return 0;
}

输出 36

inline 关键字要求编译器将函数调用替换为函数体,这里的输出是正确的,因为它首先计算表达式然后传递。它减少了函数调用开销,因为不需要存储返回地址,函数参数不需要堆栈内存。

宏与内联函数的比较:

  1. 宏通过替换工作,而在 inline Functions 中,函数调用被替换为主体。
  2. 宏由于替换而容易出错,而内联函数可以安全使用。
  3. 宏没有地址,而内联函数有地址。
  4. 宏难以与多行代码一起使用,而内联函数则不然。
  5. 在 C++ 中,宏不能与成员函数一起使用,而内联函数可以。

结论:

内联函数有时比宏更有用,因为它改进了 性能,使用安全,也减少了函数调用开销。 这只是对编译器的请求,某些函数不会被内联,例如:

  • 大型函数
  • 具有太多条件参数的函数
  • 递归代码和循环代码等

这是一件好事,因为每当编译器认为最好以另一种方式做事时。

【讨论】:

  • 正如备注:宏可以固定为使用括号计算相同的数字。但是,它仍然容易出错,因为您需要在实施过程中考虑绝对愚蠢的替代和所有情况。
【解决方案2】:
#include<iostream>
using namespace std;
#define NUMBER 10 //macros are preprocessed while functions are not.
int number()
{ 
    return 10;
}
/*In macros, no type checking(incompatible operand, etc.) is done and thus use of micros can lead to errors/side-effects in some cases. 
However, this is not the case with functions.
Also, macros do not check for compilation error (if any). Consider:- */
#define CUBE(b) b*b*b
int cube(int a)
{
 return a*a*a;
}
int main()
{
 cout<<NUMBER<<endl<<number()<<endl;
 cout<<CUBE(1+3); //Unexpected output 10
 cout<<endl<<cube(1+3);// As expected 64
 return 0;
}

宏通常比函数更快,因为它们不涉及实际的函数调用开销。

宏的一些缺点: 没有类型检查。很难调试,因为它们会导致简单的替换。宏没有命名空间,因此一段代码中的宏会影响另一段。宏可能会导致副作用,如上面的 CUBE() 示例所示。

宏通常是一个衬里。但是,它们可以包含多于一行。函数中没有这样的限制。

【讨论】:

  • 您从#define TWO_N(n) 2 &lt;&lt; ncout &lt;&lt; CUBE(TWO_N(3 + 1)) &lt;&lt; endl; 获得了多少乐趣? (最好以endl 结束输出行,而不是以它开头。)
【解决方案3】:

内联函数由编译器扩展,而宏由预处理器扩展,这仅仅是文本替换。因此

  • 在宏调用期间没有类型检查,而在函数调用期间进行类型检查。

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

    #define MAX(a,b) ((a)>(b) ? (a) : (b))
    int i = 5, j = MAX(i++, 0);
    

    会导致

    int i = 5, j = ((i++)>(0) ? (i++) : (0));
    
  • 宏参数在宏展开之前不计算

    #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`
    
  • return 关键字不能像函数一样在宏中用于返回值。

  • 内联函数可以重载

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

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

【讨论】:

    【解决方案4】:

    内联函数中如果存在任何迭代或递归语句,将表现为函数调用,以防止重复执行指令。它对节省程序的整体内存很有帮助。

    【讨论】:

      【解决方案5】:

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

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

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

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

      【讨论】:

      • 内联函数并不总是保证被内联:因为编译器不会内联,如果这样做会产生更慢的代码等。编译器做了很多工程师不能做的分析并且做正确的事情.
      • 我相信递归函数是另一个大多数编译器忽略内联的例子。
      • 在这种情况下,C 与 C++ 相比有什么重要区别吗?
      • 没有提到的一点是内联会受到编译标志的影响。例如,当您构建最大速度(如 GCC -O2/-O3)时,编译器将选择内联许多函数,但当您构建最小大小 (-Os) 时,通常会内联仅调用一次的函数(或非常小的函数)。使用宏没有这样的选择。
      • 宏不能用访问说明符(如私有或受保护)覆盖,而内联函数是可能的。
      【解决方案6】:

      内联函数类似于宏(因为函数代码在编译时在调用点展开),内联函数由编译器解析,而宏由预处理器展开。因此,有几个重要的区别:

      • 内联函数遵循对正常函数强制执行的所有类型安全协议。
      • 内联函数的指定语法与任何其他函数相同,只是它们在函数声明中包含 inline 关键字。
      • 作为参数传递给内联函数的表达式只计算一次。
      • 在某些情况下,作为参数传递给宏的表达式可以被多次计算。 http://msdn.microsoft.com/en-us/library/bf6bf4cf.aspx

      • 宏在预编译时展开,不能用于调试,但可以使用内联函数。

      -- good article: http://www.codeguru.com/forum/showpost.php?p=1093923&postcount=1

      ;

      【讨论】:

        【解决方案7】:

        从编码的角度来看,内联函数就像一个函数。因此,内联函数和宏的区别与函数和宏的区别是一样的。

        从编译的角度来看,内联函数类似于宏。它是直接注入代码中的,不会被调用。

        一般来说,您应该将内联函数视为混合了一些小的优化的常规函数​​。与大多数优化一样,由编译器决定它是否真的关心应用它。由于各种原因,编译器通常会很高兴地忽略程序员内联函数的任何尝试。

        【讨论】:

          【解决方案8】:

          在已经给出的基础上增加另一个区别:您不能在调试器中单步执行#define,但可以单步执行内联函数。

          【讨论】:

            【解决方案9】:

            在 GCC(我不确定其他人)中,声明内联函数只是对编译器的提示。最终还是由编译器来决定是否在调用时包含函数体。

            内联函数和预处理宏的区别比较大。预处理器宏只是一天结束时的文本替换。您放弃了编译器对参数和返回类型进行类型检查的许多能力。参数的评估是非常不同的(如果你传递给函数的表达式有副作用,你将有一个非常有趣的调试时间)。关于函数和宏的使用位置存在细微差别。例如,如果我有:

            #define MACRO_FUNC(X) ...
            

            其中 MACRO_FUNC 显然定义了函数的主体。需要特别小心,以便在所有情况下都能正确运行一个函数,例如一个写得不好的 MACRO_FUNC 会导致错误

            if(MACRO_FUNC(y)) {
             ...body
            }
            

            那里可以正常使用功能。

            【讨论】:

              【解决方案10】:

              首先,预处理器宏只是在编译之前在代码中“复制粘贴”。所以没有类型检查,会出现一些副作用

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

              #define max(a,b) ((a<b)?b:a)
              

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

              inline int max( int a, int b) { return ((a<b)?b:a); }
              

              【讨论】:

              • 只是想在您的示例中添加,除了副作用之外,宏还可以引入额外的工作量,考虑max(fibonacci(100), factorial(10000)) 较大的将被计算两次:(
              • 每个人都在谈论类型检查,但只是你提供了一个真实的例子,这就是我赞成这个答案的原因。
              • @watashiSHUN 为什么较大的要计算两次?提前致谢!
              • 在宏中,为什么ab会被递增两次?这是因为max(a++, b++) 将被替换为(a++ &lt; b++) ? b++ : a++,对吗?我想这也回答了我的上述问题(关于@watashiSHUN 的评论)。提前致谢!
              【解决方案11】:

              宏忽略命名空间。这让他们变得邪恶。

              【讨论】:

                【解决方案12】:

                内联函数将保持值语义,而预处理器宏只是复制语法。如果您多次使用该参数,您可能会在预处理器宏中遇到非常微妙的错误——例如,如果参数包含像“i++”这样的突变,那么执行两次是相当令人惊讶的。内联函数不会有这个问题。

                【讨论】:

                  【解决方案13】:

                  内联函数在语法上的行为与普通函数一样,为函数局部变量提供类型安全和范围,如果它是方法,则可以访问类成员。 此外,在调用内联方法时,您必须遵守私有/受保护的限制。

                  【讨论】:

                    【解决方案14】:

                    主要区别在于类型检查。编译器将检查您作为输入值传递的内容是否属于可以传递给函数的类型。预处理器宏并非如此 - 它们在任何类型检查之前被扩展,这可能会导致严重且难以检测的错误。

                    Here 是其他几个不太明显的要点。

                    【讨论】:

                      猜你喜欢
                      • 2019-10-03
                      • 1970-01-01
                      • 1970-01-01
                      • 2012-09-07
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多