宏定义函数-普通函数-内联函数区别

宏定义函数VS普通函数VS内联函数

宏定义函数VS普通函数

  1. 宏定义函数
  • 要点:变量都用括号括起来,防止出错,结尾不需要;。在实际编程中,不推荐把复杂的函数使用宏,不容易调试。多行用\

  • 例子:
    单行:
    #define MAX(a, b) ((a) > (b) ? (a):(b))
    多行:

    #define MALLOC(n, type) \
    ((type *) malloc((n)* sizeof(type))
    

    对于第一个函数,如果用普通函数,该怎样写?

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

    很显然,我们不会选择用函数来完成这个任务,原因有两个:

    1. 首先,函数调用会带来额外的开销,它需要开辟一片栈空间,记录返回地址,将形参压栈,从函数返回还要释放堆栈。这种开销会降低代码效率,而使用宏定义则在代码速度方面比函数更胜一筹;
    2. 其次,函数的参数必须被声明为一种特定的类型,所以它只能在类型合适的表达式上使用,我们如果要比较两个浮点型的大小,就不得不再写一个专门针对浮点型的比较函数。反之,上面的那个宏定义可以用于整形、长整形、单浮点型、双浮点型以及其他任何可以用“>”操作符比较值大小的类型,也就是说,宏是与类型无关的
    3. 和使用函数相比,使用宏的不利之处在于每次使用宏时,一份宏定义代码的拷贝都会插入到程序中。除非宏非常短,否则使用宏会大幅度增加程序的长度。
    4. 还有一些任务根本无法用函数实现,但是用宏定义却很好实现。比如参数类型没法作为参数传递给函数,但是可以把参数类型传递给带参的宏。比如上面的malloc的例子.
  • 总结:

属性 #define宏 函数
代码长度 每次使用时,宏代码都被插入到程序中。除了非常小的宏之外,程序的长度将大幅度增长。 函数代码只出现于一个地方:每次使用这个函数时,都调用那个地方的同一份代码
执行速度 更快 存在函数调用、返回的额外开销
操作符优先级 宏参数的求值是在所有周围表达式的上下文环境里,除非它们加上括号,否则邻近操作符的优先级可能产生不可预料的结果。 函数参数只在函数调用时求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。
参数求值 参数用于宏定义时,每次都将重新求值,由于多次求值,具有副作用的参数可能会产生不可预测的结果。 参数在函数调用前只求值一次,在函数中多次使用参数并不会导致多次求值过程,参数的副作用并不会造成任何特殊问题。
参数类型 宏与类型无关,只要参数的操作是合法的,它可以用于任何参数类型。 函数的参数是与类型有关系的,如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务是相同的。
  1. 补充: 普通函数的调用堆栈情况.
    比如:
    void P(){
        phase 1;
        Q();
        phase 2;
    }
    
    void Q() {
        phase 3;
        R();
        phase 4;
    }
    
    P( ),Q( ),R( )在经过编译器变为汇编后, 都有自己的代码位置,比如
    0x0000400A P:
                xxx     #phase1
                call Q
                xxx     #phase2
                ret
    0x0000400B Q:
                yyy     #phase3
                call R
                yyy     #phase4
    0x0000400C R: 
                zzz     #phase
                zzz     #phase
    
    而对应的栈帧呢:
    ---
    P的参数入栈
    返回地址1(phase2的地址,即函数Q后的一下条语句)
    ---
    Q的参数入栈
    返回地址2(phas4的地址,即函数R后的一下条语句)
    ---
    R的参数
    ---
    
    当R函数调用完毕,则R的栈帧出栈,同时将返回地址2pop,返回给rip,这样就继续执行一下条语句了.Q调用完毕也是同理的,所以这就是普通函数的调用情况.
    很显然,普通函数调用在汇编后,这个函数只有一份代码量,调用它就入栈出栈它的地址,造成一定的开销.而对于宏定义来说,只是简单的文本替换,这在预处理时就替换过了,故实际上你可以认为就没有宏定义出来的函数,所以当然不会有这样的开销.

普通函数VS内联函数 内联函数VS宏

例子:

inline void P() {
    phase P1;
    phase P2;
}

void Q() {
    phase1;
    P();
    phase2;
}

P是一个内联函数,对于Q来说编译后得到的汇编是什么样子呢?

Q:
    xxx     #phase 1
    yyy     #phase P1
    zzz     #phase P2
    xxx     #pahse 2

我们可以发现,Q的汇编代码中并没有对P的调用(call),而是直接将P的代码拷到Q中,这样的好处就是由于没有call ret,所以就没有普通函数调用带来的出栈入栈开销,速度更快,但增加代码量,增加内存开销.

那么内联函数又有哪些不同?
内联函数本质上还是一个函数,只是在调用时不必call ret罢了.

  • 内联函数是一个真正的函数,遵循函数的类型和作用域规则,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确,这样就消除了它的隐患和局限性;
    即: inline void func(int i,int j);调用这个时候,我必须符合参数类型才能正确调用,而宏直接替换就完事了,是类型无关的.
  • 内联函数可以作为某个类的成员函数,这样就可以在其中使用所在类的保护成员及私有成员;
  • 宏是不加任何验证的简单代码替换,除非万不得已,不要使用。

注意:
内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间占比很小;如果代码执行时间很短,则内联函数就可以节省函数调用的时间。

  • 含有递归调用的函数不能设置为inline;
  • 使用了复杂流程控制语句:循环语句和switch语句,不能设置为inline;
  • 由于inline增加体积的特性,所以建议inline函数内的代码应很短小。最好不超过5行;
  • 内联函数应该在头文件中定义,关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。
发布了54 篇原创文章 · 获赞 14 · 访问量 1万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览