分类: C/C++
2009-01-19 22:53:44
利用嵌入汇编代码的方法,计算n个整数的平均值,下面是代码:
|
int avg(int num, …); 这个函数返回num(num>=1)个整数的平均值,
很显然,它的调用约定必须为__cdecl, main函数调用它的时候, 需要main函数自己平衡堆栈,嵌入的汇编代码用来计算这num个整数的总和。
下面我们来分析一下这个程序的主要执行过程:
main定义了一个临时变量result用来保存平均值,即avg函数的返回值。
在进入这个avg函数之前,main函数需要传递所有的变量进去:
push 9
push 4
push 7
push 8
push 2
push 5
此时的栈空间如下:
|
|
|
|
… |
|
5 |
=> esp |
2 |
|
8 |
|
7 |
|
4 |
|
9 |
|
…… |
|
__cdecl调用方式参数传递是从右到左。当参数全部传入进去之后,就会调用这个函数:
0040D7B4
call @ILT+5(_avg) (0040100a) 调用函数
0040D7B9 add esp,18h 平衡堆栈
当call指令执行的时候,call的返回地址会被压入栈:
|
|
…. |
|
Call返回地址 |
=> esp |
5 |
|
2 |
|
8 |
|
7 |
|
4 |
|
9 |
|
在avg函数内部, 首先ebp的值会被压入栈保存,并且esp当前的值会被送入到ebp
push ebp
mov ebp, esp
…. |
|
Ebp的值 |
Esp <= ebp指向这里 |
Call返回地址 |
Ebp + 4 |
5 |
Ebp + 8 |
2 |
Ebp + 12 |
8 |
Ebp + 16 |
7 |
Ebp + 20 |
4 |
Ebp + 24 |
9 |
Ebp + 28 |
于是我们就可以通过ebp加上一个偏移来访问被main函数入栈的参数
ebp + 4是call调用的返回地址, ebp + 8 则是第一个参数num(这里是5)值,后面的num个就是我们要获取的整数,如果用个循环,
for ( i=0;i<5;i++), 那么我们要获取的参数的将是[ebp+(3+i)*4], 这种写法
mov ebx, [ebp+(3+i)*4]虽然在VC6下可以编译通过, 但是是错误的, 正确的代码如下:
; eax = (3+i)*4
mov eax, i ; 将i的值送入eax
add eax, 3 ; eax = 3 + i
shl eax, 2 ; eax = (3+i)*4
mov ebx, [ebp +eax] ; 将[ebp + (3+i)*4]的内容,即每个参数,送入ebx
add sum, ebx ; 将结果累加起来,放在临时变量sum中
我们利用ebp寄存器,非常轻易的访问了所有被压入栈的参数,这比直接使用c语言要简洁高效。但我们需要注意一点的是,必须非常清楚各个参数被压入栈的顺序,以及ebp寄存器当前指向的位置。
关于调用约定问题,可以参考这篇文章:
调用方式__cdecl和__stdcall的异同点