这篇文章小编写了三个小时,汇编代码分析确实很容易将自己绕进去,但请大家不要放弃,自己分析一遍之后,绝对会收获很多,小编在分析的过程中也遇到一些问题,但是最后也是将其一一解决掉。感觉自己也有很大的收获
一、形参与实参的区别
- 形参:形参出现在函数定义中,在整个函数体内都可以使用, 离开该函数则不能使用。
- 实参: 实参出现在主调函数中,进入被调函数后,实参变量也不能使用 。
注意:实参可以是一个实数,可以是一个变量,也可以是一个表达式
二、形参与实参在程序中的作用
在学习了一年的编程后,无论是C语言的函数还是Java的方法(本质都是一样的)都会大量的使用。所以也就对他们的内部一些关系慢慢有了感悟。下面我就把自己的理解,和自己找的相关资料总结一下,如下所示:
形参和实参的功能是作数据传送。主函数通过实参的传递,从而间接性的将两部分
不同的代码块给联系起来,从而达到既不互相干扰(如果你将变量的地址随意传入,
并对其造成影响发生干扰,这只能说是你在故意的恶搞),但又相辅相成。在汇编
级的代码大家很容易就会发现,主函数的代码块和数据块,与其他函数的代码块数据
块处于不同的地方,但是在call指令的作用下,主函数又与各个函数联系起来,这
就再一次证明我所说的“既不相互干扰,但又相辅相成”。发生函数调用时, 主调
函数通过把实参的值传送给被调函数的形参,从而实现主调函数向被调函数的数据传
送 。因此使各个之独立的功能代码块形成巨大的工程项目。
三、汇编代码与C语言代码结合分析
接下来,让我们先来看一段C语言代码:
#include <stdio.h>
int fun(int count1, int count2);
int fun(int count1, int count2) {
int sum = count2 + count1;
return sum;
}
int main() {
int num1 = 1;
int num2 = 2;
fun(num1, num2);
return 0;
}
接下来,在看与之对应的部分汇编代码(对于部分基础指令在上一篇文章里已经介绍过:基础汇编(一))。看不懂也没关系,接下来我将会对每一部分进行解释。
_TEXT SEGMENT
_count1$ = 8
_count2$ = 12
_sum$ = -4
_fun PROC NEAR
; File a.c
; Line 5
push ebp
mov ebp, esp
push ecx
; Line 7
mov eax, DWORD PTR _count2$[ebp]
add eax, DWORD PTR _count1$[ebp]
mov DWORD PTR _sum$[ebp], eax
; Line 9
mov eax, DWORD PTR _sum$[ebp]
; Line 10
mov esp, ebp
pop ebp
ret 0
_fun ENDP
_TEXT ENDS
PUBLIC _main
_TEXT SEGMENT
_num1$ = -4
_num2$ = -8
_main PROC NEAR
; Line 13
push ebp
mov ebp, esp
sub esp, 8
; Line 14
mov DWORD PTR _num1$[ebp], 1
; Line 15
mov DWORD PTR _num2$[ebp], 2
; Line 17
mov eax, DWORD PTR _num2$[ebp]
push eax
mov ecx, DWORD PTR _num1$[ebp]
push ecx
call _fun
add esp, 8
; Line 19
xor eax, eax
; Line 20
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
C语言执行是从主函数开始,所以我们先从主函数看起,主函数的汇编代码如下;
PUBLIC _main
_TEXT SEGMENT
_num1$ = -4
_num2$ = -8
_main PROC NEAR
; Line 13
push ebp
mov ebp, esp
sub esp, 8
; Line 14
mov DWORD PTR _num1$[ebp], 1
; Line 15
mov DWORD PTR _num2$[ebp], 2
; Line 17
mov eax, DWORD PTR _num2$[ebp]
push eax
mov ecx, DWORD PTR _num1$[ebp]
push ecx
call _fun
add esp, 8
; Line 19
xor eax, eax
; Line 20
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
我们先分析一下这两条语句:_num1$ = -4,_num2$ = -8。很明显这是我们声明在主函数里的两个变量,但是-4,-8是干什么的呢?这里小编告诉大家,这个是用来计算存储地址的。具体是怎么算的,让我们看看Line 17这里面的两条语句:_num2$[ebp], _num1$[ebp]。在上篇文章中我们介绍过这个语句里存在一步计算[ebp + _num2$]和[ebp + _num1$],该计算就是为变量num2和num1计算存储地址的语句。
我们接着往下看Line13 这部分首先通过push指令将栈底指针ebp原先的值(我们给原先的值起个名字,叫old)保存在栈底,然后再用mov将栈顶指针esp的值赋值给栈底指针ebp,从而使得他们两个都先指向栈底的首地址,形成一个空栈。如下图
接着通过这条sub esp, 8,给变量num1,num2申请8B空间。为什么通过减法运算(sub)申请空间?大家只需要记住,给栈顶指针esp进行加法(加正数或者减负数)操作指针往栈底方向移动,从而释放栈空间。给栈顶指针esp进行减法(减正或者加负)操作指针往栈顶方向移动,完成申请空间。(看到这里相信大家对上面的-4,-8理解了吧,esp-4就是num1的首地址,同时将值给eax。然后push放进栈里。esp-8就是num2的首地址,同样的给ecx,并且放到栈里)并且每当给给栈里放一个数据,栈顶指针会自动往上移动4B,所以在下图中,当存入num2后esp指向了12B的位置
,申请空间后在Line 14,Line 15部分对变量num1和num2进行空间地址计算,同时赋值。Line 17 这一部分就是将实参给形参,并且放入到堆栈里,如下图所示:
接着调用call _fun会将调用函数语句的下一行代码的地址(也就是返回地址)放入到栈里,以便在函数块执行完后可以正确的找到返回的位置。之后开始执行fun函数的代码:
_TEXT SEGMENT
_count1$ = 8
_count2$ = 12
_sum$ = -4
_fun PROC NEAR
; File a.c
; Line 5
push ebp
mov ebp, esp
push ecx
; Line 7
mov eax, DWORD PTR _count2$[ebp]
add eax, DWORD PTR _count1$[ebp]
mov DWORD PTR _sum$[ebp], eax
; Line 9
mov eax, DWORD PTR _sum$[ebp]
; Line 10
mov esp, ebp
pop ebp
ret 0
_fun ENDP
_TEXT ENDS
首先执行Line 5这部分代码,将ebp的值(此时ebp的值是栈底的首地址)放入到到栈里,然后将esp赋值给ebp,ecx入栈。Line 7 就是完成count1 + count2 然后赋值给sum。Line 9就是将返回值存入到eax。如下图所示:
接下来将ebp赋值给esp 也就是将栈顶指针下移,从而将ecx空间释放。pop ebp将栈底指针再次下移到开始时候的栈底起始位置。在函数执行完之后,栈顶指针下移到12这个位置。此时再次回到主函数,执行add esp, 8。从而将形参变量的空间也释放掉。如下图:
Line 19就是主函数里的return 0;Line 19部分,首先把ebp赋值给esp,释放掉num1,和num2的变量空间。然后pop ebp 。再次使得ebp的值变成old,回到之前的位置去。程序结束。
由以上推到过程,我们可以知道,形参的空间申请和将实参赋值给形参是在main函数里面完成的
以上就是这段汇编代码的运行过程。