80X86 上的函数 / 过程调用 .
call 指令来调用过程 ;ret 指令 (return) 返回调用程序 .过程如下:
1)确定 执行完过程后要返回的指令地址 (返回/链接地址).
2)将该地址保存 到已知位置.
在没有递归时,可将其放在任意位置.
放到内存中的栈 是最常见的,执行过程:call->push/ret->pop
优点 :
1)栈”后进先出”,完全支持嵌套和递归 .
2) 栈在内存中可高效操作 . 不同过程的返回地址可重用同一内存空间 .
3)频繁 访问内存的栈时可以使用缓存 .
4)栈也是保持活动记录 的场所.
3)通过跳转 机制将控制转移到过程的第一条指令 .
4)CPU开始执行 ,直到遇到 ret 指令为止.
5)ret 取出返回地址后,将控制转移到该地址 .
PowerPC 上的 bl(branch then link) 指令.
将控制转移到其操作数指令的目标地址 ; 并将 bl 后第一条指令的地址拷贝到链接寄存器 . 进入过程后 , 如果没有代码修改链接寄存器的值 , 过程就通过 blr(branch to LINK register) 指令返回调用处 ; 若函数要将链接寄存器挪作它用 , 就要负责将返回地址保存 , 并在函数调用结束处 ( 即将通过 blr 指令返回前 ) 恢复链接寄存器的值 .
两者的比较.
call/ret 指令是复杂指令 ( 一条指令完成多个独立任务 ). 其额外开销与维护栈的开销需要评定 . 而 bl 通过 link 地址的间接跳转指令 , 没有维护软件栈的开销 , 相对较快 .
blz指令必须包含操作码和函数的偏移量 .如果函数太远,超出了预留的偏移量位数范围,就只能生成一串指令来计算目标例程的地址,以便控制间接转移到哪里.在大程序时会这样.还有编译器不知道目标函数(外部函数)地址的时候只能使用最长形式的函数调用.
叶函数/过程.
在展平调用树时切勿牺牲可读性和可维护性.只调用其他函数,其他什么事情都不干的函数/过程最好不要写.应使调用树尽可能的浅且更多的叶过程 .
叶函数一般不会修改链接寄存器 , 所以无需额外代码保存链接寄存器的值 .
宏和内联函数
调用函数 的一个特性:开销总是固定的 ,不管函数体有一条指令,还是一千条.其设置参数和创建,销毁活动记录的指令数都是一样的.所以我们应该尽量放置较大的过程或函数 ,而将短序列作为内联代码使用 .
一方面要编程结构模块化 ,另一方面,过度频繁地调用某过程将要求过大的开销 .平衡两者不容易.
纯粹的宏在调用过程/函数的地方将后者展开为过程/函数体 .由于没有调用和返回代码,宏的展开避免了与call/ret指令相关的开销.宏使用参数文本替换,而非将参数压入栈或传送到寄存器.不足是如果宏本身很大,又在不同的地方调用,那么可执行程序的体积将会膨胀.其是时间与空间的折中.
内联 : 支持内联的语言并不保证会将代码内联展开 . 这由编译器做主 : 如果函数体太大 , 或参数过多 , 不会展开 ; 即使将其展开 , 仍会存在一些纯宏代码没有的开销 ( 要创建活动记录来处理局部变量 , 临时需要 ) .
传递参数
传递的参数数据越多,调用函数的开销越大.若干的可选参数带来通用性的好处,但是也会增大开销 .
通过值传递大型数据 ,编译器就得生成相关的机器代码,将该数据的值复制到过程的活动记录中 .这相当耗时.另外,CPU寄存器组可能放不下大型数据,造成更大的开销.建议通过引用方式来传递大值.
传递小值数据时 , 通过值 / 引用的效率高低取决于所用的 CPU 和编译器 .
存放数据的全局变量可供函数调用,调用该函数时就不需要额外指令来传递数据 .于是减少了调用开销.但是过多使用全局变量使得编译器很难优化程序 .使用全局变量可减少调用函数的开销,但同时阻止了各种可能的优化 .
参数传递机制
传递值:调用过程的代码对参数数据生成一份拷贝,将此拷贝传递给过程 .优势是CPU只需将参数视为活动记录里的局部变量 .
传递引用:好处:1)参数总是占据固定数目的内存 (指针变量的尺寸),而与原参数的尺寸无关.2)能够修改实参的值 .缺点:访问引用参数的开销大 ,因为子过程需要在每次访问数据前解析其地址 (为了使用寄存器间接寻址模式解析指针,就得将此指针调入寄存器).
posted @
2014-06-10 11:23
robynhan
阅读(
301 )
评论()
编辑
收藏
举报