内联函数 C/C++

1. 内联函数基本概念       

在C/C++语言中,如果一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗。为了解决这个问题,C语言中我们可以使用宏片段定义来实现代码片段预编译替换。在C++中由于空间概念的出现,内联函数可以用来替代C语言中的代码片段。编译器会直接将函数体插入到函数调用的地方,内联函数没有普通函数调用时的额外开销(压栈、跳转、返回)。

        在C++中不推荐使用#define对变量或者某个常量进行定义,这样违背了C++的空间隔离特性,推荐使用const替代需要define的地方。那么对于define定义的代码片段怎么处理呢?C++中推荐使用内联函数替代宏代码片段。

        C++编译器直接将函数体插入到函数调用的地方,内联函数没有普通函数调用时的额外开销(压栈、跳转、返回),C++编译器不一定满足函数的内联请求。内联函数具有普通函数的特征,参数检查,返回类型等。

#include 

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

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

int main(int argc, char *argv[])
{
    int a = 1;
    int b = 3;
    int c = FUNC(++a, b);

    printf("a = %d\n", a);
    printf("b = %d\n", b);
    printf("c = %d\n", c);

    return 0;
}

        将宏定义注释掉,换成内联函数调用的形式,结果如下:a=2,b=3,c=2。可见宏代码块带来的副作用,调用内联函数的输出才是正确的结果。对于内敛函数其对应的机器码并未被合理优化掉,还是使用函数调用的方式,这也就说明,现代编译器大部分情况下是不会对内联函数进行优化的,除非加了强制内联编译宏。g++使用__attribute__((always_inline))属性,当然我们推荐开发程序首选推荐使用内联字样对函数进行合理定义。

func(int, int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     eax, DWORD PTR [rbp-4]
        cmp     eax, DWORD PTR [rbp-8]
        jge     .L2
        mov     eax, DWORD PTR [rbp-4]
        jmp     .L4
.L2:
        mov     eax, DWORD PTR [rbp-8]
.L4:
        pop     rbp
        ret
.LC0:
        .string "a = %d\n"
.LC1:
        .string "b = %d\n"
.LC2:
        .string "c = %d\n"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        mov     DWORD PTR [rbp-20], edi
        mov     QWORD PTR [rbp-32], rsi
        mov     DWORD PTR [rbp-4], 1
        mov     DWORD PTR [rbp-8], 3
        add     DWORD PTR [rbp-4], 1
        mov     edx, DWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, edx
        mov     edi, eax
        call    func(int, int)
        mov     DWORD PTR [rbp-12], eax
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, DWORD PTR [rbp-8]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC1
        mov     eax, 0
        call    printf
        mov     eax, DWORD PTR [rbp-12]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC2
        mov     eax, 0
        call    printf
        mov     eax, 0
        leave
        ret

C++中内联函数有一定的限制要求:(1)要求内联函数不能使用任何形式的循环语句;(2)不能存在过多的条件判断语句;(3)函数体不能过于庞大;(4)不能对函数进行取地址操作;(5)函数内联声明必须在调用语句之前。在现代编译器中,编译技术非常优秀,以上的限制也不是完全不能存在的,上述程序中就存在一个for循环。只要函数不太过于夸张,现代编译器都是可以内联的。

2. 小结

  C++中可以通过inline声明内联函数

  编译器直接将内联函数扩展到函数调用的地方

  inline只是一种请求,编译器不一定允许这种请求

  内联函数省去了函数调用时的压栈,跳转和返回的开销

  • inline只适合涵数体内代码简单的函数数使用,
  • 不能包含复杂的结构控制语句例如while、switch
  • 内联函数本身不能是直接递归函数(自己内部还调用自己的函数)。

内联函数的使用也有需要注意的地方,因为栈空间就是指放置程式的局部数据也就是函数内数据的内存空间,在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程式出错的问题,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。

内联能提高函数的执行效率,为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?

内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收

获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

以下情况不宜使用内联:

(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。

(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline 不应该出现在函数的声明中)。

宏定义

优点:宏函数在预处理期间会进行宏替换,没有函数压栈开销,运行效率高。
缺点:

1.不安全(不会进行类型检测)
2.代码复用率不高
3.不停的进行替换,增长代码长度
4.不能调试

宏和内联函数的区别
1.内联函数采用的是值传递,而宏定义采用的是对等替换.
2.宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销
3.编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
————————————————
版权声明:本文为CSDN博主「wang yang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Romantic_C/article/details/81489479

总结

因此,将内联函数放在头文件里实现是合适的,省却你为每个文件实现一次的麻烦.而所以声明跟定义要一致,其实是指,如果在每个文件里都实现一次该内联函数的话,那么,最好保证每个定义都是一样的,否则,将会引起未定义的行为,即是说,如果不是每个文件里的定义都一样,那么,编译器展开的是哪一个,那要看具体的编译器而定.所以,最好将内联函数定义放在头文件中.

你可能感兴趣的:(C++,C,c++,开发语言,c语言)