持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第35天,点击查看活动详情
C语言语句的机器级表示
过程调用的机器级表示
int add(int x,int y){
return x + y;
}
int main(){
int t1 = 125;
int t2 = 80;
int sum =add(t1,t2);
return sum;
}
3个问题: 以下过程(函数)调用对应的机器级代码是什么?
如何将t1(125)、t2(80)分别传递给add中的形式参数x,y
add函数执行的结果如何返回给caller ( main ) ?
过程调用的执行步骤(P为调用者,Q为被调用者)
( 1)P将入口参数(实参)放到Q能访问到的地方;
(2 )P保存返回地址,然后将控制转移到Q;
( 3 )Q保存P的现场,并为自己的非静态局部变量分配空间;
(4)执行Q的过程体(函数体)
(5)Q恢复P的现场,释放局部变量空间
(6 )Q取出返回地址,将控制转移到P。
揭秘:
现场:通用寄存器的内容
为何要保护现场?因为所有过程共享一套通用寄存器
想象:妈妈和你做菜时共用一套盘子的情况。
IA-32的寄存器使用约定
- 调用者P保存寄存器:EAX、EDX、ECX: Q可直接使用这三个寄存器 若P在从Q返回后还要用的话,P应在转到Q之前先保存,并在从Q返回后先恢复它们的值再使用。
- 被调用者Q保存寄存器:EBX、ESI、EDI Q必须先将它们的值保存到栈中再使用它们,并在返回P之前恢复它们的值。
- EBP和ESP分别是帧指针寄存器和栈指针寄存器,分别用来指向当前栈帧的底部和顶部。
根据上面的给出的约定思考一下,为了减少准备和结束阶段的开销,每个过程应先使用哪些寄存器?
EAX,ECX,EDX.
因为Q调用时不用保存,简化了步骤
过程调用过程中栈和栈帧的变化(Q为调用过程)
int add(int x,int y){
return x + y;
}
int caller(){
int t1 = 125;
int t2 = 80;
int sum =add(t1,t2);
return sum;
}
一个C过程的大致结构如下:
-
准备阶段
- 形成帧底:push指令和mov指令
- 形成栈帧(如果需要的话):sub指令或and指令
- 保存现场(如果有被调用者保存寄存器):mov指令
-
过程(函数)体
-
分配局部变量空间,并赋值
-
具体处理逻辑,如果遇到函数调用时
- 准备参数:将实参送栈帧入口参数处
- CALL指令:保存返回地址并转被调用函数
-
在EAX中准备返回参数
-
-
结束阶段
退栈:leave指令或pop指令
取返回地址返回:ret指令
过程调用参数传递举例
按地址传递参数:此时,a和b的值会调换
按值传递参数:此时,a和b的值不会调换。因为交换的是入口参数的位置,而不是交换局部变量所在的位置