- 内联类似于宏,在调用方法放入内部展开被调用方法,以此方法来代替方法的调用(内联显著提升性能,但同时增加了编译时间)
示例:方法调用代价(通常方法非调用/返回机制都会对寄存器进行存储和恢复操作)
寄存器介绍:
*指令指针(Instruction Pointer,IP),也成为程序计数器 :存放吓一跳将要执行的指令地址。方法调用时,跳转到被调用方法的指令并修改IP。
*链接寄存器(Link Register,LR):存储某一方法的IP地址。若该方法进行了其他方法调用,则LR地址就是被调用方法执行完毕后返回的地方。
*栈指针(Stack Pointer,SP):跟踪记录堆栈的使用情况。SP必须保存为方法调用序列的一部分。
*帧指针(Frame Pointer,FP):标识堆栈中两个区域的边界,一个区域是供调用方法保存需要记录状态的寄存器,第二个区域是为被调用方法的自变量分配内存
*自变量指针(Argument Pointer,AP):指出方法参数在堆栈中的位置
方法调用介绍:
- 使用寄存调用方法
- 整理传给被调用方法的参数,及参数压栈(一般倒序),参数压栈后,SP指向第一个参数
*返回的地址指令压栈,调用指令跳转到被调用方法的第一条指令
*被调用方法在堆栈中保存调用方法的SP、AP和FP,并调整每个“管家”寄存器以反映被调用方法的上下文环境
*被调用房保存(压栈)其将会用到的所有其他寄存器(以便被调用方法返回后不会中断调用方法的上下文,因此需要额外保存3或4个寄存器)
- 整理传给被调用方法的参数,及参数压栈(一般倒序),参数压栈后,SP指向第一个参数
- 清除调用
*有返回值,讲讲返回值存储到寄存器0或者1(这是暂时性寄存器)
*将由于方法调用而保存的寄存器从堆栈中恢复至其初始位置
*将保存的调用者的FP和AP寄存器值从堆栈中到其相应的位置
*修改SP使其指向将方法第一个参数压栈前的位置
*从堆栈中找到返回地址并将其存入IP,强制返回至调用者中紧接调用点的位置
总结:4个寄存器维护线程,2~4个供方法使用用。至少需要12个时钟周期,甚至40个。这只是方法调用开销的一半。
- 虚方法的内联:通常是间接通过函数指针表(vptr)来进行调用,因为不知道当前的对象类型。但并非完全如此:
例外:某些实例中,编译阶段已经知道其类型可能为多态指针,这种情况下对虚方法进行直接调用是可行的。
inline virtual int x::y(char *a) { ... }
void z(char *b) {
x_base* x_pointer = new x(arguments);
x x_instance(arguments);
x_pointer->y(b);
x_instance.y(b);
}
对象指针可以是多态的,如果与对象指针的关联对象是可见的,并且期间,没有用实际类型未知的对象给对象指针赋值,那么编译器在编译时就可以确定方法的哪个虚拟实例被调用了,且产生直接调用而非虚调用。在x_pointer的实例中,编译器可以确定(动态对象实例的结构是可见的,同时未对x_pointer赋值,这种赋值可能改变此基类指针引用的对象类型)关于y()的哪些虚方法需要被调用的多态解决方案。真实环境下,编译阶段会有许多虚方法调用被解析。例如,所有在虚方法内有this指针引发的对虚方法的调用都存在至少一种可能的编译器解决方案,意味着编译器足够完善,许多虚方法的调用就可以内联化的。
解释:就是x_pointer是new动态创建的,因此可以看出实例结构就是x,同时没有对x_pointer进行可以改变对象类型的赋值,因此是可以将y内联进来的。