GCC 关键字inline探究

本文详细介绍了C/C++中inline关键字的作用及使用方式。通过对比不同编译选项下的反汇编代码,展示了inline如何影响函数调用过程,从而提高程序运行效率。
一、inline介绍

先看造型:

inline int test()
{
    ......
    return 0;
}

int main()
{
    test();
    return 0;
}

我们知道,如果test函数没有inline关键字修饰的时候,程序执行到调用test的时候,会从main函数跳到test函数执行。为了从test函数返回到mian函数后,能从调用test函数的下一条指令执行,在调用test函数前,我们必须对现场进行保护(将一些寄存器的值压栈)。

那如果加了inline关键字呢,那编译系统就会将test函数当做一个宏来处理,即直接在main函数中展开。在这里需要注意的是,并不是所有函数加上inline关键字就一定会在main函数中展开,一般要求inline关键字尽可能的简单。

二、反汇编看效果

源码:

#include <stdio.h>

static inline int test1()
{
int a = 1;

printf("Hello test1.\n");

return a;
}

static inline int test2()
{
int b = 2;

printf("Hello test2.\n");

return b;
}

int main()
{
test1();
test2();

return 0;
}

第一次编译:
arm-none-linux-gnueabi-gcc inline.c -o inline

反汇编如下:
arm-none-linux-gnueabi-objdump -d inline > log

查看log文件
cat log:

部分汇编代码
000083d4 <main>:
    83d4: e1a0c00d  mov ip, sp
    83d8: e92dd800  push {fp, ip, lr, pc}
    83dc: e24cb004  sub fp, ip, #4 ; 0x4
    83e0: eb000005  bl 83fc <test1>
    83e4: eb000012  bl 8434 <test2>
    83e8: e3a03000  mov r3, #0 ; 0x0
    83ec: e1a00003  mov r0, r3
    83f0: e24bd00c  sub sp, fp, #12 ; 0xc
    83f4: e89d6800  ldm sp, {fp, sp, lr}
    83f8: e12fff1e  bx lr

000083fc <test1>:
    83fc: e1a0c00d  mov ip, sp
    8400: e92dd800  push {fp, ip, lr, pc}
    8404: e24cb004  sub fp, ip, #4 ; 0x4
    8408: e24dd008  sub sp, sp, #8 ; 0x8
    840c: e3a03001  mov r3, #1 ; 0x1
    8410: e50b3010  str r3, [fp, #-16]
    8414: e59f0014  ldr r0, [pc, #20] ; 8430 <test1+0x34>
    8418: ebffffb9  bl 8304 <_init+0x50>
    841c: e51b3010  ldr r3, [fp, #-16]
    8420: e1a00003  mov r0, r3
    8424: e24bd00c  sub sp, fp, #12 ; 0xc
    8428: e89d6800  ldm sp, {fp, sp, lr}
    842c: e12fff1e  bx lr
    8430: 00008504  .word 0x00008504

00008434 <test2>:
    8434: e1a0c00d  mov ip, sp
    8438: e92dd800  push {fp, ip, lr, pc}
    843c: e24cb004  sub fp, ip, #4 ; 0x4
    8440: e24dd008  sub sp, sp, #8 ; 0x8
    8444: e3a03002  mov r3, #2 ; 0x2
    8448: e50b3010  str r3, [fp, #-16]
    844c: e59f0014  ldr r0, [pc, #20] ; 8468 <test2+0x34>
    8450: ebffffab  bl 8304 <_init+0x50>
    8454: e51b3010  ldr r3, [fp, #-16]
    8458: e1a00003  mov r0, r3
    845c: e24bd00c  sub sp, fp, #12 ; 0xc
    8460: e89d6800  ldm sp, {fp, sp, lr}
    8464: e12fff1e  bx lr
    8468: 00008514  .word 0x00008514

第二次编译

arm-none-linux-gnueabi-gcc -O  inline.c -o inline

反汇编如下:
arm-none-linux-gnueabi-objdump -d inline > log

查看log文件
cat log:

000083d4 <main>:
    83d4: e52de004  push {lr} ; (str lr, [sp, #-4]!)
    83d8: e24dd004  sub sp, sp, #4 ; 0x4
    83dc: e59f0018  ldr r0, [pc, #24] ; 83fc <main+0x28>
    83e0: ebffffc7  bl 8304 <_init+0x50>
    83e4: e59f0014  ldr r0, [pc, #20] ; 8400 <main+0x2c>
    83e8: ebffffc5  bl 8304 <_init+0x50>
    83ec: e3a00000  mov r0, #0 ; 0x0
    83f0: e28dd004  add sp, sp, #4 ; 0x4
    83f4: e49de004  pop {lr} ; (ldr lr, [sp], #4)
    83f8: e12fff1e  bx lr
    83fc: 0000849c  .word 0x0000849c
    8400: 000084ac  .word 0x000084ac

总结如下:
对于inine关键字,如果在编译的时候不加优化选项(-O 或 -O2)时,编译系统不会将其修饰的函数在mian函数中展开。但是会把被inline修饰的函数 代码按其在main函数中的调用顺序放在main后面。
如果在编译的时候加了优化选项,被inline修饰的函数大部分情况下会在main函数中展开。

注意:inline是一个建议型关键字,具体有没有被展开反汇编一看便知。


原文地址: http://blog.chinaunix.net/uid-26833883-id-3351281.html
### GCC 13.2 中 `inline` 函数的行为特性 #### 定义与基本概念 在 C 和 C++ 编程语言中,`inline` 关键字用于建议编译器将函数的定义嵌入到调用它的位置,从而减少函数调用开销并可能提高性能。然而,在现代编译器实现中(如 GCC),`inline` 更多地被视作一种优化提示而非强制指令。 对于 GCC 13.2 版本而言,`inline` 的行为遵循 C++ 标准的规定,并在此基础上提供了一些扩展功能[^1]。以下是关于 `inline` 函数的一些重要特性和使用说明: #### 行为特性 1. **链接属性** 当一个函数被标记为 `inline` 后,默认情况下它具有内部链接(internal linkage)。这意味着即使该函数在同一程序的不同翻译单元中多次定义,也不会违反 One Definition Rule (ODR)[^1]。例如: ```cpp inline void func() { // Function body } ``` 2. **编译器决策权** 尽管开发者可以显式地指定某个函数为 `inline`,但这并不意味着编译器一定会将其展开为内联形式。实际是否执行内联取决于多种因素,包括但不限于函数大小、复杂度以及上下文环境等。GCC 提供了 `-fno-inline` 参数来禁用所有非强制性的内联操作[^2]。 3. **静态局部变量支持** 如果在一个 `inline` 函数体内存在静态局部变量,则无论此函数在哪里被实例化,都只会创建一份这样的静态对象副本。这保证了跨不同文件间的一致性[^1]: ```cpp inline int counter() { static int count = 0; return ++count; // 跨多个 TU 只有一份 'count' } ``` 4. **常量表达式的兼容性增强** 自 C++11 开始引入 constexpr 支持以来,许多原本需要通过宏或者预处理器完成的任务现在都可以借助真正的函数机制来达成目的;而到了 C++17/C++20 ,随着更灵活的语言特性加入进来之后,越来越多场景下可以直接利用 inline 结合其他关键字共同作用于更加复杂的计算逻辑之中而不必担心效率损失问题 。比如下面这个例子展示了如何构建一个既高效又安全的时间戳生成方法 : ```cpp consteval auto now_ms_since_epoch(){ using namespace std::chrono ; return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count(); } template<typename T> concept TimeStampable=T::template supports_now_v<now_ms_since_epoch>; struct Record{ explicit(TimeStampable)Record(int id):recordId{id},timestamp{now_ms_since_epoch()}{} int recordId; decltype(auto) timestamp;//decltype(auto) here acts as placeholder for non-type template parameter deduction [^1] }; ``` 5. **调试友好型改进措施** 鉴于过度依赖传统意义上的纯粹 Inline 往往会给后续维护工作造成困扰 ,因此新版 Gcc 增加了几项辅助工具帮助开发人员更好地掌控整个过程 :一方面可以通过设置特定标志位(-finstrument-functions )记录每次进入/退出某段代码片段时的相关信息以便追踪 ;另一方面则允许用户自定义规则控制哪些部分应该优先考虑成为候选目标 (-finline-limit=N ). --- ### 示例代码展示 以下是一个简单的示例,演示了如何正确声明和定义一个 `inline` 函数: ```cpp // Header file: utils.h #ifndef UTILS_H #define UTILS_H #include <string> namespace example { inline bool is_palindrome(const std::string& str) noexcept { size_t n = str.size(); for(size_t i=0;i<n>>1;++i){ if(str[i]!=str[n-i-1])return false;} return true;} } // end of namespace example #endif /*UTILS_H*/ ``` 上述代码片段中包含了几个值得注意的地方:首先是采用了头文件保护宏防止重复包含引发错误;其次是运用命名空间封装相关内容避免污染全局作用域范围内的名称空间结构体系架构设计原则得到体现出来的同时也兼顾到了可移植性强弱程度方面的考量因素影响下的取舍平衡点所在之处有所展现给读者朋友们参考学习借鉴之用意蕴含其中矣! ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值