ATL布幔之下的秘密(4)
2006-07-22 22:54:54 来源:WEB开发网现在来探究一下编译器为我们产生的代码。编译器插入这个代码来创建堆栈帧,这样它就可以通过标准方式来存取参数和局部变量了。堆栈帧是一个为函数保留的区域,用来存储关于参数、局部变量和返回地址的信息。堆栈帧通常是在新的函数调用的时候创建,并在函数返回的时候销毁。在8086体系中,EBP寄存器就被用于存储堆栈帧的地址,有时叫做栈指针。(译注:ESP和EBP在本文中都被作者笼统地称为“Stack Pointer”,事实上ESP应称作“堆栈指针[Stack Pointer]寄存器”,它指示堆栈的栈顶便宜地址;EBP应称作“基址指针[Base Pointer]寄存器”,它用来作为基地址并和偏移量组合使用来访问堆栈中的信息。)
这样,编译器首先保存前一个堆栈帧的地址,然后使用ESP的值创建新的堆栈帧。函数返回之前,先前的堆栈帧就恢复了。
现在来看看堆栈帧中都有什么。在EBP的高地址一边存放所有参数,EBP的低地址一边则存放所有的局部变量。
函数的返回地址保存在EBP中,前一个堆栈帧的地址保存在EBP + 4。现在看看下面的例子,它拥有两个参数和三个局部变量。
程序61. extern "C" void fun(int a, int b) {
现在来看看编译器产生的函数代码。
int x = a;
int y = b;
int z = x + y;
return;
}
int main() {
fun(5, 10);
return 0;
}push ebp
现在来看看_x、_y这些东西都是什么。也就是定义在函数定义上方的这些东西:
mov ebp, esp
sub esp, 12 ; 0000000cH
; int x = a;
mov eax, DWORD PTR _a$[ebp]
mov DWORD PTR _x$[ebp], eax
; int y = b;
mov ecx, DWORD PTR _b$[ebp]
mov DWORD PTR _y$[ebp], ecx
; int z = x + y;
mov edx, DWORD PTR _x$[ebp]
add edx, DWORD PTR _y$[ebp]
mov DWORD PTR _z$[ebp], edx
mov esp, ebp
pop ebp
ret 0_a$ = 8
这就意味着你可以像这样阅读代码:
_b$ = 12
_x$ = -4
_y$ = -8
_z$ = -12; int x = a;
这也就意味着参数a和b的地址分别为EBP + 8和EBP + 12。并且,x、y和z的值分别存储在内存中EBP - 4、EBP - 8、EBP - 12的位置上。
mov eax, DWORD PTR [ebp + 8]
mov DWORD PTR [ebp - 4], eax
; int y = b;
mov ecx, DWORD PTR [ebp + 12]
mov DWORD PTR [ebp - 8], ecx
; int z = x + y;
mov edx, DWORD PTR [ebp - 4]
add edx, DWORD PTR [ebp - 8]
mov DWORD PTR [ebp - 12], edx
更多精彩
赞助商链接