首页 > 其他分享 >c语言中inline用法

c语言中inline用法

时间:2023-04-29 13:11:59浏览次数:36  
标签:函数 代码 C语言 add 内联 inline 用法 语言

使用inline函数可以提升程序效率,但是让inline函数生效是有条件的...

打开 Linux 内核源代码,会发现内核在定义C语言函数时,有很多都带有 “inline”关键字,请看下图,那么这个关键字有什么作用呢?

 

inline 关键字的作用

在C语言程序开发中,inline 一般用于定义函数,inline 函数也被称作“内联函数”,C99 和 GNU C 均支持内联函数。那么在C语言中,内联函数和普通函数有什么不同呢?其实,从 inline 这个名字就应该能看出一点它的性质了——内联函数会在它被调用的位置上展开,这一点表现的和 define 宏定义是非常相似的。

将被调用的函数代码展开,操作系统就无需再在为被调用函数做申请栈帧和回收栈帧的工作,而且,由于编译器会把被调用的函数代码和函数本身放在一起优化,所以也有进一步优化C语言代码,提升效率的可能。

每发生一次函数调用,操作系统就要在程序的栈空间申请一块内存区域(栈帧),供被调用函数使用,被调用函数执行完毕后,操作系统还要回收这些内存。

 

不过,天下没有免费的午餐,C语言程序要实现内联函数的上述特性是要付出一定的代价的。普通函数只需要编译出一份,就可以被所有其他函数调用,而内联函数没有严格意义上的“调用”,它只是将自身的代码展开到被调用处的,这么做无疑会使整个C语言代码变长,也就意味着占用更多的内存空间,以及更多的指令缓存。

显然,如果滥用内联函数,cpu 的指令缓存肯定是不够用的,这会导致 cpu 缓存命中率降低,反而可能会降低整个C语言程序的效率。因此,建议把那些对时间要求比较高,且C语言代码长度比较短的函数定义为内联函数。如果在C语言程序开发中的某个函数比较大,又会被反复调用,并且没有特别的时间限制,是不适合把它做成内联函数的。

在 Linux 内核中,内联函数常常使用 static 修饰,例如:

staticinlinevoid set_value(unsignedint val){ ...}需要注意的是,内联函数必须在使用之前就定义好,否则编译器没法把这个函数展开。Linux 内核中经常像下面这样,将内联函数放在调用它的函数前面,请看C语言代码:

staticinlinevoid set_value(unsignedint val){ ...}int test_inline(){ set_value(3); ...}

所以,Linux 内核常常把内联函数定义在头文件里,这样在其他C语言代码文件开头包含头文件时,能确保内联函数在文件的最开始,无需再写额外的声明语句。

这也解释了为什么 Linux 内核为何常常使用 static 修饰内联函数,因为可以避免函数的重复定义。

前文提到内联函数的表现有些像 define 宏定义,但是为了类型安全和易读性,应优先使用内联函数而不是复杂的宏。下面通过实例进一步分析 inline 内联函数的特性。

inline内联函数的“展开代码”是什么意思?

使用过 define 写 C语言代码的朋友应该都知道,编译器在编译 C语言代码时,会将 define 定义的宏展开,而不是像普通函数那样使用 call 指令调用,例如下面这段C语言代码:

 1 #include <stdio.h>
 2 #define d_add(a, b) ((a)+(b))
 3 int f_add(int a, int b)
 4 { 
 5     return a+b;
 6 }
 7 int main()
 8 {
 9      int a = d_add(1, 2); 
10     int b = f_add(1, 2); 
11     return0;
12 }

 

 

使用 gcc -E 编译这段C语言代码,能够得到预处理后的代码如下,显然 define 定义的宏被展开了,请看:

 

使用 gcc -g 命令编译C语言代码,得到可执行文件,然后调用 objdump 命令查看汇编代码,得到如下结果:

# gcc -g t1.c # objdump -dS a.out

 

从 f_add() 函数的汇编代码也可以看出,程序首先将 2 个参数赋值给寄存器,然后使用 call 指令调用 f_add() 函数。而宏定义 d_add() 就简单了,只有一行汇编代码,这种情况下,使用 define 宏定义显然效率更高。不过,宏定义没有参数的类型检查,使用起来不太安全,好在C语言还有 inline 函数,下面再定义一个 inline 函数,请看C语言代码如下:

static inline int i_add(int a, int b){ return a+b;}

 

在 main() 函数中使用 gcc -E 命令查看添加 inline 函数后的C语言代码预处理结果,如下:

 

可以看出,在预处理阶段,inline 函数并没有像 define 宏那样展开。现在使用 gcc -g 命令编译得到可执行文件,然后使用 objdump 查看汇编代码,如下:

 

从汇编代码可以看出,inline 函数似乎并没有起到作用,i_add() 函数和 f_add() 函数的表现并没有什么不同,继续往上查看,发现编译器也将 i_add() 函数的汇编代码生成了,这无疑是将 i_add() 函数当作普通函数使用了:

staticinlineint i_add(int a, int b){ 400501: 55 push %rbp 400502: 4889 e5 mov %rsp,%rbp 400505: 897d fc mov %edi,-0x4(%rbp) 400508: 8975 f8 mov %esi,-0x8(%rbp) return a+b; 40050b: 8b 45 f8 mov -0x8(%rbp),%eax 40050e: 8b 55 fc mov -0x4(%rbp),%edx 400511: 01 d0 add %edx,%eax} 400513: 5d pop %rbp 400514: c3 retq怎么回事?不是说 inline 函数的表现和 define 宏相似,会将函数代码展开吗?其实,inline 只是建议编译器这么做,编译器究竟会不会这么做就不一定了。这与编译器的优化级别相关,请看下图:

 

gcc 的 -O 选项可以指定优化级别,我们上面编译程序时没有使用 -O 选项,因此编译器执行的是默认的 -O0,也即无优化编译。那能否在 -O0 优化级别也使用 inline 函数的特性呢?当然是可以的,只需要在定义 inline 函数时,添加 __attribute__((always_inline)) 即可,例如:

static __attribute__((always_inline)) inlineint i_add(int a, int b){ return a+b;}

 

现在再来编译C语言程序并查看汇编代码,得到如下结果:

这种情况下,编译器并没有为 i_add() 函数生成响应的汇编代码。虽然 inline 函数在预处理阶段没有像 define 宏定义那样展开,但是在生成汇编代码阶段展开了,而且参与了调用它的代码部分的优化,这显然会让整个C语言程序的效率提高。

inline 函数虽然表现上很像 define 宏定义,但是却并不能完全取代 define 宏定义,这一点在我之后的文章里会讨论,敬请关注。

小结

在 C语言程序开发中,建议把那些对时间要求比较高,且C语言代码长度比较短的函数定义为 inline 函数,这么做常常可以提升程序的效率。在默认的 -O0 编译优化项不能确保 inline 一定起作用,但是可以添加添加 __attribute__((always_inline))强制编译器对 inline 函数做相应的处理。因为 inline 函数会将自己展开,所以编译器通常不会再为 inline 生成汇编代码,不过,如果是通过函数指针的形式调用 inline 函数,编译器为了获得 inline 函数的地址,仍然会为其生成汇编代码的。

 


 

转载自:https://blog.csdn.net/weixin_33268492?type=blog

标签:函数,代码,C语言,add,内联,inline,用法,语言
From: https://www.cnblogs.com/FBsharl/p/17363891.html

相关文章

  • python高阶用法汇总——(1)高阶函数
    lambda1defsum(a,b):2returna+b3print(sum(1,5))45lab=lambdaa,b:a+b6print(lab(1,3))1-3行正常用法,5-6lambda用法。lambda:冒号之前的全是参数,即函数括号里面的 sum(a,b)冒号之后的是表达式,即return的结果。lambda只能写在一行。一般情况下,我们不使用......
  • C语言函数大全-- s 开头的函数(2)
    C语言函数大全本篇介绍C语言函数大全--s开头的函数(2)1.setlinestyle1.1函数说明函数声明函数功能voidsetlinestyle(intlinestyle,unsignedupattern,intthickness);设置当前绘图窗口的线条样式、线型模式和线条宽度参数:linestyle:线条样式,取值范围......
  • C语言复习
    环境配置(Windows、Linux、Mac)https://fishc.com.cn/forum.php?mod=forumdisplay&fid=329&filter=typeid&typeid=571java与C语言的对比linux运行代码转义字符define把所有出现的标识符全部转换为常量写法:#define标识符常量例如#defineURL"www.baidu.com"#defineD......
  • 语法:neither的用法详解
    neither的用法详解 1.做副词,意为“也不”,此时相当于nor常用结构为:(1)“neither+连系动词be(am,is,are)/助动词(do/does/did)/情态动词(should,will,must,can,couldandsoon)+主语”表示“……也不”此时,可用nor替换,这是一个倒装结构,表示前面否定的情况也同样属于后者......
  • go语言 数组和切片、可变长参数、maps、字符串、指针、结构体、方法、接口
    数组和切片数组#1定义,初始化,使用#2数组是值类型数字,字符串,布尔,数组,都是值类型,真正直接存数据切片,map,指针引用类型,是个地址,指向了具体的值#3数组长度#4循环打印数组#5多纬数组#6数组定义并赋初值,把第99赋值为1,其他都是0#数组的长度也......
  • C语言刷leetcode——并查集
    目录概述参考链接:刷题入门题:547.省份数量(朋友圈)684.冗余连接概述https://leetcode.cn/problems/number-of-provinces/solution/python-duo-tu-xiang-jie-bing-cha-ji-by-m-vjdr/基本概念并查集是一种数据结构并查集这三个字,一个字代表一个意思。并(Union),代表合并查(Find),......
  • 使用 ChatGPT ,通过自然语言编写 eBPF 程序和追踪 Linux 系统
    eBPF是一项革命性的技术,起源于Linux内核,可以在操作系统的内核中运行沙盒程序。它被用来安全和有效地扩展内核的功能,而不需要改变内核的源代码或加载内核模块。今天,eBPF被广泛用于各类场景:在现代数据中心和云原生环境中,可以提供高性能的网络包处理和负载均衡;以非常低的资源开销,做......
  • R语言,dotplot的使用
    一步步绘制和美化dotplot简介开始1.查看数据并转换为长数据格式2.绘制基础图形3.添加errorbar和pointrange4.修改颜色和坐标轴主题5.加注释总结简介作为文章中经常出现的一种图形,dotplot可以展示点的分布和统计变化之后的数据均值等特征值。以下是一篇已发表的文章中的图,......
  • java 语言与 C语言端 AES (ECB)
    注:java为no-padding注释掉了padding部分(byte数组初始化时为0x00)c为padding0x00(byte数组初始化时为0x00)代码出自网上代码地址githubhttps://github.com/mountwater/AES-128-ECB-java_and_cJAVA代码//CopyrightPopaTiberiu2011//f......
  • c语言中,字符数组名 与 指向字符串常量的指针之间的关系
    chara[]="hello";//定义一个字符数组a,constchar*b="hello";//定义一个指向字符的指针b,指向字符串常量的第一个字符的首地址区别:a是一个指针常量,它本身的值不能修改,即char*consta;b是一个常量指针,它所指向的值不能修改,constchar*b;......