天书夜读 中的一个例子,没有看明白,还请牛人可以帮忙解答下,谢谢
函数和堆栈的关系密切,这是因为:我们通过堆栈把参数从外部传入到内部。此外,我们在堆栈中划分区域来容纳函数的内部变量。
调用push 和pop 指令的时候,寄存器esp 用于指向栈顶的位置。栈顶总是栈中地址最小的位置。push 执行的结果,esp 总是减少。
pop 则增加。
C 语言所写的程序中,堆栈用于传递函数参数。这时称为调用栈。
写一个简单的函数如下:
void myfunction(int a,int b)
{
int c = a+b;
}
这是标准的C 函数调用方式.其过程是:
1) 调用方把参数反序的压入堆栈中。
2) 调用函数。
3) 调用方把堆栈复原。
而被调用函数需要做以下一些事情:
1) 保存ebp. ebp 总是被我们用来保存这个函数执行之前的esp 的值。执行完毕之后,我们用ebp 恢复esp.同时,调用我们的上
层函数也用ebp 做同样的事情。所以我们之前先把ebp 压入堆栈。返回之前弹出,避免ebp 被我们改动。
2) 保存esp 到ebp 中。
上面两步的代码如下:
push ebp ;保存ebp,并把esp 放入ebp 中
;此时ebp 与esp 同。
mov ebp,esp ;都是这次函数调用时的栈顶。
3) 在堆栈中腾出一个区域用来保存局部变量。这就是常说的所谓局部变量在栈空间中。方法为把esp 减少一个数值。这样等于压
入了一堆变量。日后要恢复时,只要把esp 恢复成ebp 中保存的数据就可以了.
4) 保存ebx,esi,edi 到堆栈中。函数调用完后恢复。这是一个编程规范。
对应的代码如下:
sub esp,0cch ;把esp 往上移动一个范围,等于在堆栈中放出一片新
;的空间用来存局部变量.
push ebx ;下面保存三个寄存器:ebx,esi,edi,这也是C 规范.
push esi
push edi
5) 把局部变量区域初始化成0cccccccch。0cccccccch 实际是INT 3.这是一个中断指令。因为局部变量不可能被执行,如果执行
了必然程序有错,这时发生中断来提示开发者。这是VC 编译DEBUG 版本的特有操作。
相关代码如下:
lea edi,[ebp-0cch] ;本来是要mov edi,ebp-0cch,但是mov 不支持-操作。所
;以对ebp-0cch 取内容,而lea 把内容的地址也就是ebp
;-0cch加载到edi 中.目的是把保存局部变量的区域(从
;ebp-0cch开始的区域)初始化成全部0cccccccch.
mov ecx,33h
mov eax,0cccccccch
rep stos dword ptr [edi] ;拷贝字符串
6) 然后做函数里应该做的事情。参数的获取是ebp+8 字节为第一个参数,ebp+12 为第二个参数,依次增加。ebp+4 字节处是
要返回的地址。
7) 恢复ebx,esi,edi ,esp,ebp,最后返回。
代码如下:
pop edi ;恢复edi,esi,ebx
pop esi
pop ebx
mov esp,ebp ;恢复原来的ebp 和esp,让上一个调用的函数正常使用.
pop ebp
ret
上面是书中的分析:疑问如下
在第2)步中,ebp被赋予了esp的值,这时ebp等于esp,那第6)步为什么又说ebp+8 字节为第一个参数,ebp+12 为第二个参数,ebp+4 字节处是
要返回的地址。
此时ebp+8,ebp+12怎么会是参数的地址呢?我觉得应该是ebp-8和ebp-12才对啊,(因为第3)步说:把esp 减少一个数值。这样等于压
入了一堆变)
还有一个就是第5)步中mov ecx,33h这一句,这一句应该是控制循环的次数的,但是又为什么这个循环的次数是33h呢?没明白!!