当前位置:编程学习 > C/C++ >>

C++函数调用栈空间结构探究&《程序员的自我修养》纠错

杨力祥老师在C++课后给同学留了一道思考题,即探讨C++函数调用时其内存的结构究竟是什么样的。在参考《程序员的自我修养》的过程中,对于书上的描述有些疑惑,因此自己在VS2008的环境下,对程序1进行了反汇编,并随着单步调试的进行察看了内存的变化,发现书上给出的图和描述存在一些小错误。在此将实际的过程记录下来。
 
//程序1
#include <iostream>
using namespacestd;
int foo(inti){
    int a= 1, b= 2;
    b = a;
    return i;
}
int main(){
    int a =foo(0xABCDEF);
    return 0;
}
首先要说明的是,程序运行时的实现是根据编译器的不同而不同的,因此需要强调,本文中讨论的所有过程都是在VS2008的编译环境下进行的,且考察的是debug版本的反汇编。

图1 运行时的栈结构
图1 是函数调用时栈的结构,下面来具体说一说汇编指令每一步都做了什么。
要事先说明的是EBP指针和ESP指针,前者在一个函数的运行过程中(没有调用其他函数时)是一个固定值,它指向的位置如图1所示;后者是栈顶指针。

操作方
对应汇编
内存操作
1
main
函数
push  0ABCDEFh
函数参数压栈
ESP指向位置1
2
main
函数
call  foo (11C1028h)
在调用的时候,会自动将调用函数之后下一条要执行的指令地址(也就是所谓的返回地址)压栈
ESP指向位置2
3
foo
函数
push ebp
mov  ebp,esp
将旧的EBP值压栈;
ESP指向位置3
将当前ESP值赋给EBP,所以EBP也指向位置3
4
foo
函数
sub esp,0D8h
在栈上给局部变量预留空间
ESP指向位置4
5
foo
函数
push  ebx
push  esi
push  edi
寄存器压栈保存
ESP指向位置5
6
foo
函数
lea edi,[ebp-0D8h]
mov ecx,36h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
将位置4的地址值赋给EDI寄存器;
将位置4到位置3之间的内存全部赋值为0CCCCCCCCh(由低到高)
此时ESP指向位置5
7
foo
函数
mov  dword ptr [a],1
mov  dword ptr [b],2
通过指针访问,将局部变量区域中的a和b赋值;
注意:a和b并不是连续存放在栈中的,也没有将局部变量的空间占满。
ESP位置不变
8
foo
函数
mov eax,dword ptr [a]
mov dword ptr [b],eax   www.zzzyk.com
通过EAX寄存器来完成局部变量的赋值操作;依然是指针访问。
ESP位置不变
9
foo
函数
mov eax,dword ptr [i]
8,9,10三步实现foo函数返回;
通过EAX寄存器来传递返回值
ESP依然指向位置5,即最后一个被压栈的寄存器
10
foo
函数
pop  edi
pop  esi
pop  ebx
将此前压栈的寄存器出栈
ESP指针指向位置4
11
foo
函数
mov  esp,ebp
pop  ebp
将EBP值赋给ESP指针,意味着释放掉了局部变量的内存空间
ESP指向位置3
将此前压栈的旧EBP值出栈,赋给EBP
ESP指向位置2
12
foo
函数
ret
函数返回,会根据此前压栈的返回地址跳转到下一条指令的位置
ESP指向位置1
13
main
函数
add  esp,4
释放此前压栈的foo函数参数,至此,foo函数彻底结束了它的生命
ESP指向位置0
14
main
函数
mov dword ptr [a],eax
通过EAX传递返回值,将返回值赋给变量a;
main函数中有关调用foo函数的内容至此结束。
 需要说明的是,在函数完成了现场保护之类的初始化工作之后,ESP会始终指向当前函数的栈空间顶,此时,若当前函数又调用了另一个函数,则会将此时的EBP视为旧EBP压栈,而与新调用的函数有关的内容则会从当前ESP所指向位置开始压栈。
 

补充:软件开发 , C++ ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,