首页 > 技术文章 > 内联函数和宏

zoneofmine 2017-02-15 16:27 原文

  为什么要使用宏呢?因为函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。而宏只是在预处理的地方把代码展开,不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个函数更有效率。
  但是宏也有很多的不尽人意的地方。
  1. 宏不能访问对象的私有成员。
  2. 宏的定义很容易产生二意性。


  我们举个例子:

#define SQR(x) (x*x)

  我们用一个数字去调用它,SQR(10),这样看上去没有什么错误,结果返回100,是正确的,但是如果我们用SQR(10+10)去调用的话,我们期望的结果是400,而宏的调用结果是(10+10*10+10),结果是120,这显然不是我们要得到的结果。避免这些错误的方法,一是给宏的参数都加上括号。

#define SQR(x) ((x)*(x))

  这样可以确保不会出错,但是即使用了这种定义,这个宏依然有可能出错,例如使用SQR(a++)调用它,他们本意是希望得到(a+1)*(a+1)的结果,而实际上呢?我们可以看看宏的展开结果: (a++)*(a++),如果a的值是4,我们得到的结果是5*6=30。而我们期望的结果是5*5=25,这又出现了问题。事实上,在一些C的库函数中也有这些问题。例如: Toupper(*pChar++)就会对pChar执行两次++操作,因为Toupper实际上也是一个宏。

 

  我们可以使用内联函数来解决这些问题。而且事实上我们可以用内联函数完全取代预处理宏。

  为什么inline能取代宏?
  1、 inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。
  2、 很明显,类的内联函数也是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数    一样。这样就消除了它的隐患和局限性。
  3、 inline 可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。

  内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。内联函数与带参数的宏定义进行下比较,它们的代码效率是一样,但是内联欢函数要优于宏定义,因为内联函数遵循的类型和作用域规则,它与一般函数更相近,在一些编译器中,一旦关上内联扩展,将与一般函数一样进行调用,比较方便。

  内联函数必须是和函数体申明在一起,才有效。像这样的申明inline SQR(int x)是没有效果的,编译器只是把函数作为普通的函数申明,我们必须定义函数体。

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

我们也可以将定义在类的外部的函数定义为内联函数,比如:

class MathClass
{  
private:   
    int i;
    int j;
 
public:   
    int Add() { return i+j; }  

    inline int Dec() { return i-j;} 
  
    int GetI(); 

}; 

inline int MathClass::GetI(){ return i; }

  上面类中申明的三个函数都是内联函数。注意,在C++中,在类的内部定义了函数体的函数,被默认为是内联函数。而不管你是否有inline关键字。

  内联函数在C++类中,应用最广的,应该是用来定义存取函数。我们定义的类中一般会把数据成员定义成私有的或者保护的,这样外界就不能直接读写我们类成员的数据了。对于私有或者保护成员的读写就必须使用成员接口函数来进行。如果我们把这些读写成员函数定义成内联函数的话,将会获得比较好的效率。

class MathClass
{  
private:   
    int i;
    int j;
 
public:   
    inline int GetI() { return i;} 
  
    inline void SetJ(int num) { j = num; }

}; 

  

  当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样了。

 

  inline函数的规则

  1. 含有递归调用的函数不能设置为inline;

  2. 使用了复杂流程控制语句:循环语句和switch语句,无法设置为inline;

  3. 由于inline增加体积的特性,所以建议inline函数内的代码应很短小。最好不超过10行。

  4. inline仅做为一种“请求”,特定的情况下,编译器将不理会inline关键字,而强制让函数成为普通函数。出现这种情况,编译器会给出警告消息。

  5. 在你调用一个内联函数之前,这个函数一定要在之前有声明或已定义为inline,如果在前面声明为普通函数,而在调用代码后面才定义为一个inline函数,程序可以通过编译,但该函数没有实现inline。

  6. 为了调试方便,在程序处于调试阶段时,所有内联函数都不被实现。

  7. 在一个文件中定义的内联函数不能在另一个文件中使用。它们通常放在头文件中共享。 

  8. 内联函数不能为虚函数,原因在于虚表机制需要一个真正的函数地址,而内联函数展开以后,就不是一个函数,而是一段简单的代码。即使虚函数被声明为内联函数,编译器遇到这种情况根本不会把这样的函数内联展开,而是当做普通函数来处理。

 

  如何禁止函数进行内联?如果使用VC++,可以使用/Ob命令行参数。当然,也可以在程序中使用 #pragma auto_inline达到相同的目的。

 

推荐阅读