码迷,mamicode.com
首页 > 其他好文 > 详细

C函数的调用过程   栈帧

时间:2016-01-17 16:21:24      阅读:800      评论:0      收藏:0      [点我收藏+]

标签:c语言   栈帧     函数调用的具体过程

C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。

     首先,栈是从高地址向低地址延伸的。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。

先来看一个代码

#include <stdio.h>
void fun()
{
    int tmp = 10;
    int*p = (int*)(*(&tmp + 1));
    *(p - 1) = 20;
}
int main()
{
    int a = 0;
    fun();
    printf("a = %d\n", a);
    return 0;
}

这个代码输出的a的值时多少呢?  答案是20。

调用fun()函数后居然把a的值改掉了技术分享,从上面的代码可以看到并没有给fun()函数传a的地址,那么fun()函数为什么会把a的值改掉呢?  回答这个问题之前我们需要了解C语言函数的调用过程。


为了更好的观察到上述函数的调用过程,我们在VC 6.0环境下查看一下它的汇编代码。


在这之前首先要知道:

   寄存器ebp称为“基址指针”,在未受改变之前始终指向栈底,用途是:在堆栈种寻址。

    寄存器esp称为“栈指针”,会随着数据的入栈出栈移动,也就是说始终指向栈   顶。


8:    int main()

       9:    {

0040D700   push        ebp                                         ;ebp入栈,即将调用main()的函数的栈底地址 放入堆栈,                                                                                                                                  以便在执行完main()之后,继续执行调用main()的函数

0040D701   mov         ebp,esp                                 ;将esp的值给ebp,此时ebp指向新的栈底  

0040D703   sub         esp,44h                                    ;   esp减44h, 为main函数开辟空间

0040D706   push        ebx                                          ;   ebx入栈,esp减1, 指向新的栈顶

0040D707   push        esi                                            ;   esi入栈,  esp减1 ,指向新的栈顶

0040D708   push        edi                                          ;edi入栈, esp减1,指向新的栈顶

0040D709   lea         edi,[ebp-44h]                         ;将ebp-44h即ebx esi edi入栈前esp的地址放入edi

0040D70C   mov         ecx,11h                                 ;  为ecx初始化

0040D711   mov         eax,0CCCCCCCCh           ;   为eax初始化

0040D716   rep stos    dword ptr [edi]                    ;将ebp的地址 存入edi

10:       int a =0;                   

0040D718   mov         dword ptr [ebp-4],0             ;为变量a开辟空间,地址为ebp-4, 将0放入该空间 

11:       fun();

0040D71F   call        @ILT+5(_fun) (0040100a)        ;  首先将main函数中将下一条指令的地址入栈esp                                                                                                                                                    减1,指向新栈顶 , 然后跳到0040100a,即fun()                                                                                                                                                                             入口地址  


******************************************************************************************************    

0040100A   jmp         fun (00401010)                       ;跳转到00401010,开始执行fun()


2:    void fun()

3:    {

00401010   push        ebp                                          ;ebp入栈,即将main()的栈底地址放入堆栈

00401011   mov         ebp,esp                                  ;将esp的值赋给ebp,此时ebp指向fun()的栈                                                                                                                                                                 底,该空间放有main()栈底地址      

00401013   sub         esp,48h                                     ; esp-48h,为fun()开辟空间

00401016   push        ebx                                           ;   ebx入栈,esp减1, 指向新的栈顶

00401017   push        esi                                             ;   esi入栈,esp减1, 指向新的栈顶

00401018   push        edi                                           ;edi入栈, esp减1,指向新的栈顶

00401019   lea         edi,[ebp-48h]                          ;将ebp-48h即ebx esi edi入栈前esp的值放入edi

0040101C   mov         ecx,12h                                  ;  为ecx初始化

00401021   mov         eax,0CCCCCCCCh            ;  为eax初始化

00401026   rep stos    dword ptr [edi]                    ;将ebp的值 存入edi

4:        int tmp =10;

00401028   mov         dword ptr [ebp-4],0Ah        ;为变量tmp开辟空间,地址为ebp-4,将10放入该                                                                                                                                            空间

5:        int*p = (int*)(*(&tmp+1));

0040102F   mov         eax,dword ptr [ebp]           ;将ebp的值(main()的栈底地址)放到eax中

00401032   mov         dword ptr [ebp-8],eax        ;将eax的内容放到ebp-8,即为p开辟的空间,此                                                                                                                                                            时p的内容为 main()栈底地址

6:        *(p-1) =20;                                

00401035   mov         ecx,dword ptr [ebp-8]        ;将p的内容放到ecx中,此时ecx存放的是main()                                                                                                                                                   栈底的地址

00401038   mov         dword ptr [ecx-4],14h          ;将14h放到ecx减4即main()中的栈底地址减4即a                                                                                                                                                             的地址,这时a的值被改为14h,也就是20

7:        }

0040103F   pop         edi                                             ;   edi出栈,esp加1,指向新栈顶

00401040   pop         esi                                             ;esi出栈,esp加1,指向新栈顶

00401041   pop         ebx                                           ;ebx出栈,esp加1,指向新栈顶


00401042   mov         esp,ebp                                    ;将ebp的地址给esp,即销毁为fun()开辟的空间

00401044   pop         ebp                                           ;ebp出栈,esp 减1,指向新栈顶,                                                                                      

00401045   ret                                                               ;返回,继续执行main()


*******************************************************************************************************

12:       printf("a = %d\n",a);          

0040D724   mov         eax,dword ptr [ebp-4]           ;将a的值放入eax中

0040D727   push        eax                                             ;eax入栈,esp减1,指向新栈顶

0040D728   push        offset string "a = %d\n" (00422fa4)   

0040D72D   call        printf (00401070)

0040D732   add         esp,8                                           ;esp加8

13:       return 0;

0040D735   xor         eax,eax                                         ;将eax中的内容清零

14:   }

0040D737   pop         edi                                                ;edi出栈,esp加1,指向新栈顶                                  

0040D738   pop         esi                                                 esi出栈,esp加1,指向新栈顶

0040D739   pop         ebx                                               ;ebx出栈,esp加1,指向新栈顶

0040D73A   add         esp,44h                                       ;esp加44h,销毁为main()开辟的空间

0040D73D   cmp         ebp,esp                                     ;比较ebp和esp的地址,用于堆栈平衡检测

0040D73F   call        __chkesp (004010f0)                    ;返回检测结果

0040D744   mov         esp,ebp                                       ;将ebp的值给esp,即调用main()的函数的栈底地址

0040D746   pop         ebp                                               ;ebp出栈,esp减1,指向新栈顶,此时esp为调                                                                                                                                                 用main()的函数的栈顶,并且指向调用main()的函                                                                                                                                                       数的下一条指令的地址 ,call-->next                                                                             

0040D747   ret                                                                 ;返回,继续执行调用main()的函数


函数调用过程如下图所示:

 图中有部分esp移动的过程没有画出,但最终不管如何入栈出栈esp会在销毁空间后指向创建空间前esp最后指向的位置

技术分享          从上面的汇编代码,我们可以看到fun()函数是通过找到变量a所在的栈的栈底地址,进而找到a的地

址,将a的内容改掉,这就解释了为什么没有给fun()传a的地址,却可以改变a的值。


总结:

        函数在调用另一个函数之前会保存两个信息(1)函数调用完返回之后下一条指令的地址

                                                                                          (2)该函数的栈底的地址


技术分享




最后分享一个使用ebp修改程序执行顺序的代码,并且最后esp回到原处,貌似什么都没发生的样子。


但是却在屏幕上多输出了一个funtest

#include <stdio.h> 
void*p =NULL; 
void*q =NULL; 
void funtest()
{
    int tmp =0;
    int tmp2 =1;
    printf("funtest\n");
    *(int*)(&tmp+2) =p; 
}
void swap(int *pa, int *pb) 
{
    int tmp=0;
    p= *(&tmp+2);
    q= &tmp+2;
    *(&tmp+2) = &funtest;
    tmp = *pa;
    *pa = *pb;
    *pb = tmp;
}
int main() 
{
    int a =10;
    int b =20; 
    swap(&a,&b); 
    printf("main\n");
    _asm{
        sub esp,4
        } 
    return 0;
}





本文出自 “蜗牛” 博客,请务必保留此出处http://15129279495.blog.51cto.com/10845420/1735749

C函数的调用过程   栈帧

标签:c语言   栈帧     函数调用的具体过程

原文地址:http://15129279495.blog.51cto.com/10845420/1735749

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!