当前位置: 首页 > news >正文

深入理解内联函数(C语言)

目录

  • 1.什么是内联函数
  • 2.内联函数与宏
  • 3.编译器对内联函数的处理
  • 4.参考文献

1.什么是内联函数

很多人都会知道,可以将比较小的函数写成内联函数的形式,这样会节省函数调用的开销,具体是什么样的开销呢?
一个函数在执行过程中,如果需要调用其他函数,则一般会执行下面的过程。

  1. 保存当前函数现场
  2. 跳到调用函数执行
  3. 恢复当前函数现场
  4. 继续执行当前函数

一个C语言程序,在main()函数中对某些数据进行处理,运算结果暂时保存在R0寄存器中。接着调用另一个函数call_fun(),调用结束后,返回main()函数继续执行。如果我们在call_fun()函数中要用到R0寄存器,就会改变R0中的值,当我们返回main()中继续执行程序的时候,就会出现错误的计算。

处理办法很简单,就是在跳到call_fun()之前,先将R0中的值保存到对战中,调用结束后,再将其值取出来,这样就可以顺利地执行main()函数了。这就是所谓的现场保存和恢复。

对于一般的函数调用,当然没有什么问题,但如果需要调用的函数本来就很小(指令和数据都不多),这个时候如果频繁地调用,就会出现频繁地保存现场,恢复现场,降低了程序的执行效率,这个时候就可以将call_fun()改写为内联函数,简单高效。

2.内联函数与宏

内联函数和宏的功能差不多,为什么不直接定义一个宏,而去定义一个内联函数呢?二者又有什么不同呢?

与宏相比,内联函数具有以下优势。

  • 参数类型检查:内联函数具有宏的展开特性,但本质仍是函数,在编译过程中,编译器仍然可以对其进行参数检查,而宏不具备此功能。
  • 便于调试:函数支持丰富的断点调试功能,而宏定义不支持,这样便于软件的调试和开发。
  • 接口封装:有些内联函数可以用来封装一个接口,而宏并无此特性。

3.编译器对内联函数的处理

众所周知,并不是在函数前添加了inline关键字,程序在执行过程中就会乖乖执行内联展开,这与开发者和计算机都有关系。

而若要得知函数是否真正进行了内联展开,则需要深入底层,从汇编程序中得知。

尺有所短,寸有所长,内联函数也有缺点。内联函数会增大程序的体积,如果在一个文件中多次调用内联函数,多次展开,则整个函数的体积就会变大,降低了代码的执行效率。这与函数的设计初衷相悖(函数的作用之一就是提高代码的复用性)。

编译器在对内联函数做展开时,除了检测用户定义的内联函数是否有指针、循环、递归,还会在函数执行效率和函数调用开销之间进行权衡。一般来说,从程序员角度来说,主要考虑以下因素。

  • 函数体积小。
  • 函数体内无指针赋值、递归、循环等语句。
  • 调用频繁。

下面的例子,我们用一个简单的程序实现了某个数的阶乘。
在这里插入图片描述
我们封装了两个函数,都含有linline关键字,而从编译后的汇编程序可以看出,一个函数进行了内联展开,而另一个可能考虑到了函数并不是很精简,并未对其进行内联展开。

然后将优化等级调到了1,再看看结果:
在这里插入图片描述
可以看到,将两个函数都进行了内联展开。输出结果仍然是24,保持不变。

接下来我们使用GCC编译器提供的特性__attribute__来实现强制内联:
在这里插入图片描述
可以看到,此时即使关闭了优化等级,编译器还是对两个内联函数进行了内联展开。程序的运行结果也不会受影响。

此次我们采用了在线的编译工具,感觉还不错,喜欢的同学可以试试。
Compiler Explorer

4.参考文献

《嵌入式C语言自我修养》

相关文章:

  • YOLO系列模型疑问
  • python:__set_name__使用
  • Algoriddim djay Pro Ai for Mac:AI引领,混音新篇章
  • windows 下nginx常用命令
  • 本地图片先压缩,再上传
  • 中国电子学会(CEIT)2023年09月真题C语言软件编程等级考试四级(含详细解析答案)
  • 【Shell】sed编辑器实例
  • 上下文视觉提示实现zero-shot分割检测及多visual-prompt改造
  • SpringBoot中如何在服务器进行校验?
  • 基于51单片机的盆栽自动浇花系统
  • STM32F103 标准库介绍及PWM波控制LED亮度
  • CnosDB:深入理解时序数据质量函数
  • MFC GDI 绘图模式、映射模式、画笔、笔、字体
  • 题解:CF859C Pie Rules
  • tcpdump源码分析
  • 【刷算法】求1+2+3+...+n
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • Apache Spark Streaming 使用实例
  • CODING 缺陷管理功能正式开始公测
  • Hexo+码云+git快速搭建免费的静态Blog
  • Java IO学习笔记一
  • java多线程
  • LintCode 31. partitionArray 数组划分
  • OSS Web直传 (文件图片)
  • PAT A1050
  • rc-form之最单纯情况
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • Webpack 4 学习01(基础配置)
  • 安装python包到指定虚拟环境
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 浅谈Golang中select的用法
  • 悄悄地说一个bug
  • 深度解析利用ES6进行Promise封装总结
  • 收藏好这篇,别再只说“数据劫持”了
  • kubernetes资源对象--ingress
  • # Pytorch 中可以直接调用的Loss Functions总结:
  • # 消息中间件 RocketMQ 高级功能和源码分析(七)
  • #职场发展#其他
  • (4)STL算法之比较
  • (8)STL算法之替换
  • (a /b)*c的值
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (rabbitmq的高级特性)消息可靠性
  • (力扣记录)235. 二叉搜索树的最近公共祖先
  • (七)Knockout 创建自定义绑定
  • (未解决)macOS matplotlib 中文是方框
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • *算法训练(leetcode)第三十九天 | 115. 不同的子序列、583. 两个字符串的删除操作、72. 编辑距离
  • .dwp和.webpart的区别
  • .net Application的目录
  • .NET Core 将实体类转换为 SQL(ORM 映射)
  • .net core 连接数据库,通过数据库生成Modell
  • .net 重复调用webservice_Java RMI 远程调用详解,优劣势说明
  • .NET大文件上传知识整理
  • .NET开发人员必知的八个网站