C++的inline

inline是C++/C的重要特性,主要用于减少函数调用时入栈和出栈操作带来的消耗,但是却使得我们的程序可能会变得庞大,所以说这是一个权衡的事情,典型的“以空间换取时间”的策略。可是,这个策略在很多情况下是可行的,因为相对于空间而言,时间往往显得更加重要一些。
但是inline函数的使用还有一些问题需要注意的,这里总结如下。

inline函数是声明不是定义

C++中的inline函数是一个声明不是定义,这一点需要额外注意。请看以下代码,首先我们有一个a.h头文件:

/*************************************************************************
    > File Name: a.h
    > Author: Baniel Gao
    > Mail: createchance@163.com 
    > Created Time: Tue 23 Aug 2016 09:58:43 PM CST
 ************************************************************************/

#include <iostream>
using namespace std;

inline void func(int a, int b);

很清晰,这里的func好像是说声明了一个func函数,并且他是inline函数。然后它的定义是在a.cpp文件中:

/*************************************************************************
    > File Name: a.cpp
    > Author: Baniel Gao
    > Mail: createchance@163.com 
    > Created Time: Tue 23 Aug 2016 09:59:13 PM CST
 ************************************************************************/

#include "a.h"

inline void func(int a, int b)
{
    cout << a << " " << b << endl;
}

这里好像是给出了func的定义,然后我们在test.cpp中使用这个func函数:

/*************************************************************************
    > File Name: test.cpp
    > Author: Baniel Gao
    > Mail: createchance@163.com 
    > Created Time: Mon 22 Aug 2016 09:58:20 PM CST
 ************************************************************************/

#include "a.h"

int main()
{
    func(1, 2);

    return 0;
}

一切看起来是那么地正常,但是编译的时候出错误了:
这里写图片描述
这个错误的意思是说:gcc编译的时候出现警告说我们使用了func函数但是并没有定义这个函数,因此我们在ld链接的时候出现找不到func这个函数的错误。这是为什么呢?就是因为inline函数同通常的函数是不一样的,inline函数更像是一个放在别的地方的代码块,在编译的时候gcc编译器会将这个代码拷贝到调用它的地方,这样做的好处就是可以减少运行时的函数调用压栈和出栈的时间消耗,坏处就是增加最终结果文件的体积;这也是它卫为什么叫做inline函数的原因,因为它是一个嵌入的代码块函数。因此,从这个意义上看,inline函数和.h头文件中的声明意义是一样的,只是说有这个代码,然后在用的时候拷贝过去。我们其实可以验证我们上面说的事情,那就是我们在编译的时候给出–save-temps选项,留下编译过程中产生的所有中间文件,我们打开a.s汇编文件看下就知道了:

    .file   "a.cpp"
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .text
    .type   _Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB1029:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    cmpl    $1, -4(%rbp)
    jne .L1
    cmpl    $65535, -8(%rbp)
    jne .L1
    movl    $_ZStL8__ioinit, %edi
    call    _ZNSt8ios_base4InitC1Ev
    movl    $__dso_handle, %edx
    movl    $_ZStL8__ioinit, %esi
    movl    $_ZNSt8ios_base4InitD1Ev, %edi
    call    __cxa_atexit
.L1:
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1029:
    .size   _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
    .type   _GLOBAL__sub_I_a.cpp, @function
_GLOBAL__sub_I_a.cpp:
.LFB1030:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $65535, %esi
    movl    $1, %edi
    call    _Z41__static_initialization_and_destruction_0ii
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1030:
    .size   _GLOBAL__sub_I_a.cpp, .-_GLOBAL__sub_I_a.cpp
    .section    .init_array,"aw"
    .align 8
    .quad   _GLOBAL__sub_I_a.cpp
    .hidden __dso_handle
    .ident  "GCC: (Debian 4.9.2-10) 4.9.2"
    .section    .note.GNU-stack,"",@progbits

可以看到,这里并没有一个叫做func的函数,因此上面编译的时候test.s在编译的时候想要调用func时就出现了找不到func函数的错误了。正确的做法是将所有的inline函数都放在.h头文件中就可以了,因为inline函数本身的意义就是一个声明并不是定义。另外,在C++中,如果你在.h头文件中定义了一个类,并且给出这个类中函数的实现,那么这个函数就是inline的,不管你有没有显式说明。

inline有的时候不是inline

真正的inline最终是有gcc决定的,因为inline不是运行时的事情,而是编译时的事情。gcc会最终决定你的inline是不是会真正当成一个inline来处理,通常来讲gcc会根据你的inline函数长度大小来决定,如果你的inline函数实在过于庞大,那么gcc在编译的时候极有可能会给出警告或者错误,甚至默认忽略你的inline声明。毕竟,inline不是给庞大函数设计的,而是那些频繁调用,小巧玲珑的函数设计的。
有的时候,如果你加上了gcc的-O优化选项后,你的一些短小的非inline函数也会变成inline函数,这样可以在一定程度上提高代码的运行效率。

### C++ 中 `inline` 关键字的用法与最佳实践 #### 定义与作用 在 C++ 中,`inline` 关键字用于建议编译器将函数定义为内联函数。这意味着当调用该函数时,编译器可能会将其替换为实际的函数体代码,从而减少函数调用开销并提高性能[^1]。 然而需要注意的是,`inline` 只是一个提示而非强制命令;最终决定权在于编译器优化策略。如果函数过大或者复杂度较高,则即使标记为 `inline`,也可能不会被真正内联化。 #### 使用场景 通常情况下,只有那些非常简单、执行时间短且频繁使用的函数才适合声明为内联函数。对于较大的方法实现部分不应该直接嵌入到类定义内部,除非它们极其基础或是出于性能考虑而设计得极为紧凑简洁的小型辅助功能模块。 另外,在跨平台开发过程中涉及到未托管代码(unmanaged code)交互时,考虑到不同编程环境下的兼容性问题以及微软关于结构体型大小不超过 16 字节 的推荐标准等因素,合理选用合适的数据传递方式变得尤为重要。在这种背景下,采用基于对象实例化的操作模式往往能够更好地满足多语言支持需求,并提供更加灵活便捷的应用接口给终端使用者[^2]。 #### 示例代码展示 下面给出一段简单的例子来说明如何正确运用 `inline`: ```cpp // 正确的做法:只对小型、常驻内存中的计算逻辑应用 inline class MathUtils { public: static inline int square(int value) { return value * value; } // 合适的候选者 private: double piApproximation; }; int main() { cout << "Square of 5 is: " << MathUtils::square(5); } ``` 上述案例展示了在一个工具性质较强的静态成员函数上加注 `inline` 是恰当合理的做法之一,因为它的业务处理流程较为单一明了而且重复利用率高。 #### 性能考量及其他注意事项 尽管适当使用 `inline` 能够带来一定层面上的效率提升,但过度依赖它反而可能导致可读性维护难度增加等问题出现。因此,在具体项目实践中应当遵循如下原则: - 避免把过长或复杂的算法封装成所谓的 “Inline Function”; - 对于那些仅存在于头文件内的自由形式(non-member) 函数同样可以附加此属性以便促进潜在的机会共享同一份副本以节省空间资源消耗; - 如果某个特定版本之后引入的新特性已经包含了隐式的内建机制(比如现代编译技术自动识别热点路径),那么显式指定可能就显得多余甚至干扰正常工作流了。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值