C++ 中的内联函数

在C/C++中, 如果有一些函数被频繁调用, 那么就会不断的进行函数入栈出栈的操作, 这会造成栈空间以及程序运行时间的消耗. 栈空间就是指放置程序的局部数据以及函数内数据的内存空间, 在正常情况下, 栈空间都是有限的, 如果频繁大量的使用就会造成栈空间不足的问题, 函数的死循环递归调用的最终结果就是导致占内存空间的枯竭

特征:

  • 相当于把内联函数里面的内容直接写在了调用内联函数处 (类似于#define或者typedef的那种替换操作)
  • 相当于不用执行进入函数的步骤, 直接执行函数体
  • 相当于 , 却比宏多了类型检查, 真正具有函数特性(这也是与#define的区别所在)
  • 在类声明中定义的函数, 除了虚函数的其他函数都会自动隐式的成为内联函数
  • 关键字inline必须与函数定义体放在一起才能使函数成为内联, 仅将inline放在函数声明前不起任何作用
  • 与非内联函数不同的是, inline函数必须在调用该函数的每个文本文件中定义. 当然, 对于同一程序的不同文件, 如果inline函数出现的话,其定义必须相同.

优点:

  • 内敛函数会像宏函数一样在被调用处展开, 省去了参数压栈, 栈帧开辟, 结构返回等步骤, 从而提高了程序的运行速度
  • 内联函数相比宏函数来说, 在代码展开处会做安全检查和类型转换(同普通函数), 而宏定义函数则不会
  • 在类声明中同时定义的成员函数, 会自动转化为内联函数, 因此 内联函数可以访问类的成员变量, 宏定义则不能.
  • 内联函数在运行时可以调试, 而宏定义则不行(因为宏定义是被预定义处理的, 所以不会有人黑的编译符号和调试信息, 调试的时候基本只能用肉眼去看)

缺点:

  • 代码膨胀. 内联函数是以代码膨胀(复制)为代价, 消除函数调用带来的开销. 如果执行函数体内代码的时间, 相比于函数调用的开销较大, 那么效率的收获就会很少. 另一方面, 每一处内联函数的调用都要复制代码, 将使程序的总代码量增大, 消耗更多的内存空间.
  • inline函数无法随着函数库升级而升级. inline函数的改变需要重新编译, 不像非内联函数那样可以直接链接
  • 是否内联, 程序员不可控, 内联函数只是对编译器的建议, 对于最终实现的决定权在于编译器.

虚函数可以是内联函数吗

  • 虚函数在语法上可以是内联函数, 内联是可以修饰虚函数的, 但是当虚函数表现多态性的时候是不能内联的. (在具体实现时, 到底是否内联, 是由编译器决定的), 如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <cstdio>
    struct Base {
    virtual ~Base() {}
    virtual void Foo() { printf("Base::Foo()\n"); }
    };
    struct Derived: Base {
    virtual void Foo() { printf("Derived::Foo()\n"); }
    };

    Base* b = new Derived; // 非 static 令编译器不能在编译期知道 b 指向那个类型的对像
    int main() {
    b->Foo(); // 不可能内联
    b->Base::Foo(); // 非多态调用,可以内联(但具体是否内联由编译器决定)
    delete b;
    }
  • 内联实际上是在建议编译器内联, 而虚函数的多态性需要在运行期起作用, 编译器无法知道运行期具体调用哪个代码, 因此虚函数表现为多态性时, 不可以内联

  • inline virtual 唯一可以内联的时候是: 编译器知道所调用的对象是哪个类(如 Base::who()), 这只有在编译器具有 实际对象而不是对象的指针或引用时才会发生.

对于常见的主流编译器, 写不写 inline 有什么影响