标签:
比如你用local在栈上定义了一个局部变量LocalVar,你知道实际的指令是什么么?一般都差不多像下面的样子:
push ebp
mov esp, ebp
sub esp, 4
现在栈上就有了4各字节的空间,这就是你的局部变量。
接下来,你执行mov LocalVar, 4,那么实际的指令又是什么?是这样:
mov dword ptr [ebp-4], 4
于是,这个局部变量的“地址”就是ebp-4——显然,它不是一个固定的地址。现在需要将它的“地址”作为参数传给某个函数,你这样写:
invoke/call SomeFunc, addr LocalVar
实际生成的指令是:
lea eax, [ebp-4]
push eax
call SomeFunc
当然,你也可以写成:
mov eax, ebp
sub eax, 4
push eax
call SomeFunc
看到了,这里多了一条指令。这就是lea的好处。于是,lea又多了一个非常美妙的用途:作简单的算术计算,特别是有了32位指令的增强寻址方式,更是“如虎添翼”:
比如你要算EAX*4+EBX+3,结果放入EDX,怎么办?
mov edx, eax
shl edx, 2
add edx, ebx
add edx, 3
现在用lea一条指令搞定:
lea edx, [ebx+eax*4+3]
lea的英文解释是: Load Effective Address.(加入有效地址,开始迷惑效地址是什么???既然是有效地址与mov ax , [address] 又有什么不同呢?其实他们都是等效的。 后来知道实际上是一个偏移量可以是立即数,也可以是经过四则运算的结果,更省空间,更有效率)
参考:
http://blog.csdn.net/lostspeed/article/details/8959142
http://www.cnitblog.com/textbox/articles/51912.html
-------------------------------------------------------------------------------
亲自花了两小时研究一个小例子,VC6 Debug版本:
源程序:
#include "stdafx.h" int add(int i) { int x = i+4; return x; } int fun() { int i=10; return add(i); } int main() { int y = fun(); return y; }
汇编程序:
// 知识点: // ESP:寄存器存放当前线程的栈顶指针,压入堆栈的数据越多,ESP也就越来越小 // EBP:寄存器存放当前线程的栈底指针(base pointer) // __cdecl 所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈 // 编译器提前生成的函数跳转代码,地址准确对应: @ILT+0(?add@@YAHH@Z): 00401005 jmp add (00401030) // 执行前 ESP = 0012FED0 // 执行后 ESP = 0012FED0 无条件跳转,不需要压栈 @ILT+5(?fun@@YAHXZ): 0040100A jmp fun (00401070) @ILT+10(_main): 0040100F jmp main (004010c0) int add(int i) { 00401030 push ebp // 执行前 ESP = 0012FED0 EBP = 0012FF28 // 执行后 ESP = 0012FECC EBP = 0012FF28 (ESP减少4,第二次偏移) 00401031 mov ebp,esp // 执行后 ESP = 0012FECC EBP = 0012FECC (保护esp的值,这样esp可随便使用了) 00401033 sub esp,44h // 执行后 ESP = 0012FE88 EBP = 0012FECC 给栈增加44h的空间 00401036 push ebx // 执行后 ESP = 0012FE84 00401037 push esi // 执行后 ESP = 0012FE80 00401038 push edi // 执行后 ESP = 0012FE7C 00401039 lea edi,[ebp-44h] // 执行后 EDI = 0012FE88 0040103C mov ecx,11h 00401041 mov eax,0CCCCCCCCh 00401046 rep stos dword ptr [edi] int x = i+4; 00401048 mov eax,dword ptr [ebp+8] // 执行前 EBP = 0012FECC,执行 EBP + 8 = 0012FED4,就取到了fun函数压栈的数据 0040104B add eax,4 // 前面call的时候导致了两次减4,所以要加8。使用eax进行运算,反正eax也是空闲的 0040104E mov dword ptr [ebp-4],eax // 把eax的值放到[ebp-4]里,相当于为x赋值了 return x; 00401051 mov eax,dword ptr [ebp-4] // 函数的返回值为eax } 00401054 pop edi 00401055 pop esi 00401056 pop ebx 00401057 mov esp,ebp // 恢复ESP,即退出函数前,栈顶指针不变 00401059 pop ebp // 恢复EBP,站底指针 0040105A ret // 这里不用加44,因为add没有调用其它函数 int fun() { 00401070 push ebp 00401071 mov ebp,esp 00401073 sub esp,44h 00401076 push ebx 00401077 push esi 00401078 push edi 00401079 lea edi,[ebp-44h] 0040107C mov ecx,11h 00401081 mov eax,0CCCCCCCCh 00401086 rep stos dword ptr [edi] int i=10; 00401088 mov dword ptr [ebp-4],0Ah // i是函数内第一个局部变量,占据了[ebp-4]的位置,因此把0Ah这个值放里 15: return add(i); 0040108F mov eax,dword ptr [ebp-4] // 把这个i局部变量的值,临时放到eax里,目的是为了方便压栈 00401092 push eax // 准备调用add函数,要给它准备所有参数,即把i的值压栈(cdeclf方式)。 // 执行前 ESP = 0012FED8 EBP = 0012FF28 // 执行后 ESP = 0012FED4 EBP = 0012FF28 (ESP减少4) // 注意,ESP的值在整个CPU里是唯一的,各个函数之间会相互影响(特别是cdecl调用方式下),push会导致ESP的值减4(可观察CPU状态) 00401093 call @ILT+0(add) (00401005) // 执行前 ESP = 0012FED4 EBP = 0012FF28 // 执行后 ESP = 0012FED0 EBP = 0012FF28 (ESP减少4,第一次偏移) // 一共两次减小4 00401098 add esp,4 } 0040109B pop edi 0040109C pop esi 0040109D pop ebx 0040109E add esp,44h // 调用者清除,对应add函数造成的esp减少了44 004010A1 cmp ebp,esp 004010A3 call __chkesp (00401110) 004010A8 mov esp,ebp 004010AA pop ebp 004010AB ret int main() { int y = fun(); 004010D8 call @ILT+5(fun) (0040100a) // 呼叫语句,执行fun函数。此函数执行完毕后,会把运算结果放在eax里。 004010DD mov dword ptr [ebp-4],eax // 本地变量y占据了[ebp-4]的位置,所以需要把前面函数的执行结果eax放到这个位置 return y; 004010E0 mov eax,dword ptr [ebp-4] // 把[ebp-4]的值放到eax里,相当于main函数的返回值已经在eax里了 } 004010E3 pop edi 004010E4 pop esi 004010E5 pop ebx 004010E6 add esp,44h // 调用者清除,对应fun函数造成的esp减少了44 004010E9 cmp ebp,esp 004010EB call __chkesp (00401110) 004010F0 mov esp,ebp 004010F2 pop ebp 004010F3 ret
不知道release版本有没有这个ESP-44的问题,之所以花了2小时,就是因为这个原因。可是VC++为什么要这样做呢?难道是为了给调用函数的时候,留出足够的栈空间(够放11个参数了)?
标签:
原文地址:http://www.cnblogs.com/findumars/p/4695011.html