从汇编以及栈帧层面理解内联函数的原理

BitLuminaryMaster
• 阅读 94

宏太复杂,所以弄出内联,内联适合小函数,把函数连到程序里面,这样就直接用,不需要调用,但是它占用空间。

C++推荐
const和enum替代宏常量
inline去替代宏函数

宏缺点:
1、不能调试
2、没有类型安全的检查
3、有些场景下非常复杂,容易出错,不容易掌握

(1)概念

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

从汇编以及栈帧层面理解内联函数的原理

如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的 调用。

查看方式:

  1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
  2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不

会对代码进行优化,以下给出vs2013的设置方式)

从汇编以及栈帧层面理解内联函数的原理

从汇编以及栈帧层面理解内联函数的原理

(2)特性

编译器选择性内联,长的代码一般是call调用,短的频繁用的一般就会展开内联。一个很大的函数,你弄成内联,编译器也不会展开内联,不会鸟你。

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
  2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。下图为《C++prime》第五版关于inline的建议:

从汇编以及栈帧层面理解内联函数的原理

  1. inline不建议声明和定义分离,分离会导致链接错误 m。因为inline被展开,就没有函数地址了,链接就会找不到,内联函数不产生符号表
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
 cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
 f(10);
 return 0;
}
// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl 
f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用

函数调用过程,call到jmp,jmp调到函数栈帧。内联函数没有栈帧,直接在程序展开,不需要调用。

从汇编以及栈帧层面理解内联函数的原理

所以 内联函数不要声明与定义分离,直接在.h文件中定义,调用时会直接展开在.cpp文件,声明和定义分离是为了好看,内联函数不需要分离。

点赞
收藏
评论区
推荐文章
Easter79 Easter79
3年前
sysfs_create_group创建sysfs接口
在调试驱动,可能需要对驱动里的某些变量进行读写,或函数调用。可通过sysfs接口创建驱动对应的属性,使得可以在用户空间通过sysfs接口的show和store函数与硬件交互;Syss接口可通过sysfs\_create\_group()来创建,如果设备驱动要创建,需要用到函数宏DEVICE\_ATTR;另外总线对应BUS\_ATTR、
CuterCorley CuterCorley
4年前
C语言入门系列之9.预处理
在之前,已多次使用过以号开头的预处理命令,如包含命令include<stdio.h、宏定义命令definePI3.1415926535等。在源程序中这些命令都放在函数之外,而且一般都放在源文件的前面,它们称为预处理部分。一、无参宏定义1.基本使用无参宏的宏名后不带参数。其定义的一般形式为:cdefine标识符
Stella981 Stella981
3年前
C++系统学习之C库assert
C库之<cassertassert.h定义了一个作为标准调试工具的宏宏函数函数说明assertEvaluateassertion(macro)assert当使用assert()里,给它一个参数,即一个表示断言为真的表达式。预处理器产生测试该断言的代码。如果断言不为真,则发出一
Wesley13 Wesley13
3年前
gdb调试技巧(第二篇)
在调试的时候,我想知道某个变量、或者某个对象、或者某个结构体的数据类型。如果某个变量是foo,怎么做?ptypefoo当然也可以看某个函数的定义信息。用同样的方法。如果我想知道某个宏的值,怎么做?需要在编译的时候加上g3。加入源代码文件是hello.cgccg3hello.c调试a.out的时候,假如宏的名称是FOO
Stella981 Stella981
3年前
C# 调用 Delphi DLL
l技术实现如何逐步实现动态库的加载,类型的匹配,动态链接库函数导出的定义,参考下面宏定义即可:defineLIBEXPORT_APIextern"C"__declspec(dllexport)第一步,我先从简单的调用出发,定义了一个简单的函数,该函数仅仅实现一
Stella981 Stella981
3年前
Kotlin 1.4.30
关键词:KotlinNews内联类从1.3推出,一直处于实验状态。内联类inlineclass,是从Kotlin1.3开始加入的实验特性,计划1.4.30进入Beta状态(看来1.5.0要转正了?)。内联类要解决的问题呢,其实也与以往我们接触到的内联函数类似,大体思路就是提供某种语法,提升代码编写体验和效率
Stella981 Stella981
3年前
Sass之混合宏、继承、占位符
  1、混合宏。    当样式变得越来越复杂,需要重复使用大段的样式时,使用变量就无法达到我们目的了。这个时候混合宏就派上用场了。  而使用混合宏时,首先要声明混合宏,而声明混合宏时有两种,不带参数混合宏和带参数混合宏两种。  1.1不带参数混合宏的声明要使用关键词@mixin。例如:  @mixinborderradi
Stella981 Stella981
3年前
IDA Pro 权威指南学习笔记(十)
栈帧(stackframe)是在程序的运行时栈中分配的内存块,用于特定的函数调用如果一个函数没有执行则不需要内存,当函数被调用时就需要用到内存1.传给函数的参数的值需要存储到函数能够找到它们的位置2.函数在执行过程中可能需要临时的存储空间,通过声明局部变量来分配这类临时空间,这些变量在函数内部使用,函数调用完后,就无法再访问它们
Stella981 Stella981
3年前
Python中函数装饰器及练习
1)装饰器的理解:1、作用:在不改变原函数的基础上,给函数增加功能   2、返回值:把一个函数当作参数,返回一个替代版的函数3、本质:返回函数的函数4、应用场景:计时器、记录日志、用户登陆认证、函数参数认证2)无参函数装饰器  实例:被装饰的函数没有参数     执行结果为:  
Wesley13 Wesley13
3年前
C++ 里的常用头文件
<assert.h验证程序断言<complex.h支持复数算术运算<ctype.h字符类型<errno.h出错码<fenv.h浮点环境<float.h浮点常量<inttypes.h整型格式转换<iso646.h替代关系操作符宏<limits.h实现常量<locale.h局部类别<
Wesley13 Wesley13
3年前
VC++知识点整理
1.内联函数定义:定义在类体内的成员函数,即函数的函数体放在类体内特点:在调用处用内联函数体的代码来替换,用于解决程序的运行效率问题。一定要在调用之前定义,并且内联函数无法递归调用。2.构造函数与析构函数构造函数:用于为对象分配内存空间,对类的成员变量进行初始化,并执行其他内部管理操作。可以接受参