本文主要介绍了C++中内联函数的使用,本质上是一种以空间换时间的提高代码效率的方法,相对于C语言中宏的缺陷进行了相应的改进。本文对内联的详细实现和底层的汇编指令进行了分析,总结了内联的几种使用方法,并提供了详细的代码示例,这也是我初学C++ primer plus的学习笔记,如有错误,欢迎在评论区批评指正。
常规函数调用
int f(int i){
return i*2;
}
main(){
int a = 4;
int b = f(a);
}
//对应的汇编代码
//以下汇编代码仅仅是为了方便理解这个过程发生了什么,并不是编译器真的编译后生成的汇编代码(实际上要比这个复杂一些)
_f_int:
add ax,@sp[-8],@sp[-8]
ret //ret指令将栈中保存的地址出栈,返回mov @sp[-4],ax处进行取指令操作,即执行程序
_main:
add sp,#8
mov ax,#4
mov @sp[-8],ax
mov ax,@sp[-8]
push ax //实参值送到堆栈中去
call _f_int //函数调用在汇编中是call指令,需要将下一条指令的地址入栈保存,并修改IP寄存器跳转到标号_f_int处执行程序
mov @sp[-4],ax
pop ax
可以看到,在调用程序过程中,需要做一些额外的动作,
那么能不能将这些函数调用过程的额外开销给节省掉,从而加快代码的执行效率呢?
c++新增了inline,内联函数特性正是用于解决这个问题,它的基本思路是直接将被调用函数的代码段嵌入在对应的代码处,所以直接往下执行即可,不需要再跳转内存中另一处再跳转回来,从而避免了额外的开销,提高了代码的执行效率。但同时存在的问题是,如果在代码中多次调用内联函数,这种方法必然造成“代码膨胀”,*相当于是一种用增大代码空间来减小代码执行时间的方法
加上inline之后的效果
inline int f(int i){
return i*2;
}
main(){
int a = 4;
int b = f(a);
}
//代码其实就相当于:
main(){
int a = 4;
int b = a +a;
}
//对应的汇编代码就相当于
_main:
add sp,#8
mov ax,#4
mov @sp[-8],ax
add ax,@ap[-8]
mov @sp[-4],ax
在.h文件中声明inline ,在.cpp文件中定义时也加上inline,在main函数中包含该.h文件,一般来说这样是没问题的,但是实际上编译器会报一个warning:inline function 'void f(int ,int)'use but never difined undefined symbols for architecture x86_64: ‘f(int ,int)’,referenced from: _main in cc3kixgj.o
ld:symbol(s) not found for architecture x86_64
即编译器知道这是个内联函数,但是没找到定义,实际上编译后采用的还是函数调用方式。
编译器在同一时间只能看到一个文件,在编译main.cpp文件时,由于包含了.h文件,所以只能看到一个inline的声明,但是inline实际上想做的是将函数体代码块直接嵌入到main.cpp中的函数体内,但这个时候却看不到inline函数的定义(定义在另一个.cpp文件中),所以只能放弃内联,在这里进行一个函数调用,而在.cpp文件中看到inline,编译器说ok,你已经嵌入到main函数体中了,我不需要在这里再产生任何代码了,所以link时就找不到这段代码里的内容。
所以解决的办法是直接在.h文件中将函数定义放到这里即可,省略原型声明
// a.h 将定义放到这里,省略函数声明
inline f(int i,int j) {return i+j;}
// main。cpp
#include"a.h"
int main()
{
int f = f(1,2);
}
//不需要a.cpp文件了
//cup.h文件
class Cup {
int color;
public:
int getColor();
void setColor(int color);
};
inline int Cup::getColor() { return color;}
inline void Cup::setColor(int color) {this->color = color;}
//省略在,cpp文件中对类里面的函数的定义
//main.cpp
#include"cup.h"
int main(){
Cup::setColor(1);
Cup::getColor();
//这里就相当于直接对color进行操作,省略了函数调用的开销,相当于将函数体直接插入到这里,
//效率更高,占用的内存空间也增大了。
}
#define MyAdd1(x,y) x+y
#define MyAdd2(x,y) ( (x) + (y) )
void test01{
int ret = MyAdd1(10,20); // = 30没问题
int ret1 = MyAdd1(10,20)*20; //预期结果是30*20 = 600 ,但实际上是10+20*20 = 410;
//这是由于宏是文本替换来实现的,也可以通过加括号来改进
int ret2 = MyAdd2(10,20)*20; //这个结果就对了,是600
}
#define MyCompare(a,b) ( (a) < (b) ) ? (a) : (b)
void test02(){
int a = 10;
int b = 20;
int ret = MyCompare(a,b); //返回10没问题
int ret1 = MyCompare(++a,b); //按预期应该++a = 11 ,函数返回较小值11 ,但是实际上这里返回的是12
//因为是 ( (++a) < (b) ) ? (++a) : (b),这里执行了两次++a ,宏并不是函数传参,只是一个文本替换,所以这里出现了问题。
}
而inline内联函数是一个真正的函数,就和常规函数一样,也是按值来传递的,所以不会出现这些问题,这使得C++内联功能远远胜过C语言的宏定义。