调用函数,需要有额外开销的.
int f(int i)
{
return i*2;
}
main()
{
int a = 2;
int b = f(a);
return 0;
}
汇编语言:
_f_int:
add ax,@sp[-8],@sp[-8]
ret
_main:
add sp,#8
//sp寄存器(堆栈指针) #8立即数 程序运行过程中,每个程序有独立的堆栈,放本地变量和返回地址.
mov ax,#4 //
mov @sp[-8],ax
mov ax,@sp[-8]
push ax
call _f_int
mov @sp[-4],ax
pop ax
过程如下:
1. add sp, #8 //堆栈的指针往上移动了8, 这8个字节放a和b这两个变量。给本地分配空间完成.
mov ax, #4
ax是一个寄存器,对于x86来说,ax主要是做运算的寄存器. 让ax计算器等于4.
mov @sp[-8], ax
把ax移动sp-8的地方去, 相当于a = 4.
move ax, @sp[-8] //把sp-8又move到了ax , 等价于a变量赋给ax这件事.
- push ax //把a赋给函数i, 把ax寄存器里面的值push到堆栈里去
//函数的参数和本地变量都在堆栈里.
//其中4就是函数的参数i
- call _f_int
主要做2件事情.
6.1. 把下一条指令的地址push到堆栈里面去,
6.2. 转到调用函数f(int)中去.
此时sp -4 是它的返回地址,sp-8是i参数.
6.3. add ax, @sp[-8], @sp[-8]
把sp-8 + sp-8放到ax中去. 此时ax =8, 也就是返回的值.
6.4. move @sp[-4], ax
return的结果是由ax带回来的,
6.5. pop ax
//调用函数的时候,需要做以下几件事:
(1). 把函数的参数送到堆栈里
(2). 把返回地址推到堆栈里面去
(3). 准备返回的值
(4). push所有的东西都要pop出来
这么多额外的东西,如何避免?
C++提供内联函数解决这些问题.
inline函数: 当去调用inline函数时, 不是真的去做push call pop这些动作. 而是把那些函数的代码嵌入到调用它的地方去. 但是仍然保持它函数的独立性.
//程序的基本流程图如下:
预处理(.i) -> 编译(.s) -> 汇编(.o) -> 链接(.exe)
预处理:(1). 读取C/C++源程序,对其中的伪指令(以#开头的指令)进行处理. #define #if #ifdef #include 等
(2). 删除所有的注释
(3). 添加行号和文件名标识
(4). 保留所有的#pragma编译器指令
编译:将预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后,禅城相应的汇编代码文件.
汇编:将编译完的汇编代码翻译成机器指令,并生成可重定位目标程序的.o文件,该文件为二进制文件,字节编码是机器指令.
链接:将一个目标文件(库文件)链接在一起生成一个完整的可执行程序.
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, @sp[-8], @sp[-8]
mov @sp[-4], ax
return 0;
}
.h .cpp必须重复inline.
.cpp:产生函数
.h:调用函数看的
.cpp不放inline,编译器会认为不是inline,编译器会真的可执行文件中留下这个函数.
.h inline,编译它调用的时候,不能生产调用的代码,需要插进来.
//a.h
inline void f(int i, int j);
//a.cpp
#include "a.h"
#include <iostream>
using namespace std;
inline void f(int i,int j)
{
cout << i << " " << j << endl;
}
//main.cpp
#include "a.h"
int main()
{
f(10,8);
return 0; //程序出错
}
//同一个时刻,编译器只能看到一个文件.
//当编译main.cpp时候,只能看到inline f(); f(10,8).但不知道f长什么样子.
//当编译a.cpp时候,由于看到inline f() {}, 不需要产生任何代码.
解决方案: 把inline函数的定义,也放在.h中
//a.h
#include <iostream>
using namespace std;
// inline int f(int i,int j); 此行可省略
inline int f(int i, int j) //此函数前面加了inline,实际上相当于声明,而不是定义了.
{
cout << i << " " << j << endl;
}
//main.cpp
#include "a.h"
int main()
{
f(4,5);
return 0;
}
内联函数需要把原型和定义放在.h中,其中原型可省略. 而实际上函数定义加了inline,相当于声明
使用inline的优缺点:
1. 若程序中有好几处需要调用inline函数,就意味着程序会变长. 牺牲代码的空间,但是会降低代码额外的开销. 这是一种典型的空间换时间的策略.
使用宏也可以,但是不能做类型检查的. 建议使用inline函数代替宏.
#define f(a) (a)+(a)
main()
{
double a = 4;
printf("%d\n", f(a));
return 0;
}
inline int f(int i)
{
return i*2;
}
main()
{
double a = 4;
printf("%d\n", f(a));
}
2.若发现inline函数过于巨大,则可能会拒绝. 递归不能inline.
如果是成员函数在class的声明时, 就给出函数的body, 那些函数统统都是inline.
方法一: 在class定义内部
class Point
{
private:
int color;
public:
int getColor() { return color; } //内联函数
void setColor(int color) {
this->color = color; //内联函数
}
};
方法二:在class定义外部,.h中,成员函数定义前面加inline.
class Rectangle
{
int width, height;
public:
Rectangle(int w = 0, int h = 0);
int getWidth() const;
void setWidth(int w);
int getHeight() const;
void setHeight(int h);
};
inline Rectangle::Rectangle(int w, int h):width(w),height(h) //内联函数
{}
inline int Rectangle::getWidth() const
{ return width; }
inline or not?
1 . 若函数比较小,可以考虑做成inline.
2. 如果是循环里面频繁调用的函数, 值的把它做成inline. 因为循环调用,意味着大量的堆栈操作,所以做成inline是非常值得的.
3. 超过20行的代码,不建议inline.
4. 递归的函数不建议用inline.