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;
}
时间: 2024-08-24 12:45:25

C函数的调用过程   栈帧的相关文章

函数的调用过程——栈帧。

今天我们来看一下函数的调用过程与栈帧. 我们通过一段简单的代码和图示来介绍这个过程: #include<stdio.h> int add(int x,int y) { int z = x + y; return z; } int main() { int a = 0xaaaaaaaa; int b = 0xbbbbbbbb; int c = add(a, b); printf("run here!%d\n", c); return 0; } 将这个过程用图示表示出来: 在c

[Android Pro] 深入理解函数的调用过程——栈帧

cp :http://blog.csdn.net/x_perseverance/article/details/78897637 每一个函数被调用时,都会为函数开辟一块空间,这块空间就称为栈帧. 首先,我们了解一下不同种类的寄存器: (1)eax,ebx,ecx,edx :通用寄存器 (2)ebp:存放指向函数栈帧栈底的地址 (3)esp:存放指向函数栈帧栈顶的地址 (4)eip:程序计数器——保存程序当前正在执行指令的下一个指令的地址 接着我们以下面这段代码为例,深入到函数的调用过程中去: #

函数的调用过程(栈帧)

1.什么是栈帧? 栈帧也叫过程活动记录,是编译器用来实现函数调用过程的一种数据结构.C语言中,每个栈帧对应着一个未运行完的函数.从逻辑上讲,栈帧就是一个函数执行的环境:函数调用框架.函数参数.函数的局部变量.函数执行完后返回到哪里等等.栈是从高地址向低地址延伸的.每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息.寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址). 2.Add()函数的调用过程 我们以Add()函数为例深入的研究一

手动跟踪函数的调用过程【转】

转自:https://blog.csdn.net/ccjjnn19890720/article/details/6871036/ 今天是10月13号,不知道为什么日子过的如此的快,大概是假期的原因吧.在十一国庆以后,上了3天课又放假了...感觉研究生的生活越来越没有学生样子啦...老师在很久以前就安排了一个任务给我,叫我完成在arm板子上的视频显示,做过了前期的JPEG的显示,觉得这个问题本身不是很大.大概是自己对这种事情了解的太少,当真正的去接触的时候就觉得难度很大. 视频本身是有一帧一帧的数

使用IDA PRO+OllyDbg+PEview 追踪windows API 动态链接库函数的调用过程

http://bbs.pediy.com/showthread.php?p=1354999 标 题: [原创]使用IDA PRO+OllyDbg+PEview 追踪windows API 动态链接库函数的调用过程.作 者: shayi时 间: 2015-02-12,05:19:54链 接: http://bbs.pediy.com/showthread.php?t=197829 使用IDA PRO+OllyDbg+PEview 追踪windows API 动态链接库函数的调用过程. (本文同步更

函数递归调用过程中的调用堆栈的情况

为了加深对函数递归调用过程中的理解,本Demo程序特意在VS2008 C#控制台程序实现了阶乘的计算功能,用于观察函数递归调用过程中的调用堆栈的情况. 源码如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace RecursiveTset { class Program { //阶乘的定义:n!=n*(n-1)!,特别的,1!=1:0!=1 //阶乘的实

函数调用过程栈帧变化详解

http://www.cnblogs.com/fxplove/articles/2574451.html 数调用另一个词语表示叫作 过程.一个过程调用包括将数据和控制从代码的一部分传递到另一部分.另外,它还必须在进入时为过程的局部变量分配空间,并在推出时释放这些空间.而数据传递,局部变量的分配和释放通过操纵程序栈来实现.在了解本文章之前,您需要先对程序的进程空间有所了解,即对进程如何使用内存?如果你知道这些,下面的内容将是很easy的事情了.为了您的回顾还是将简单的分布图贴出来,便于您的回顾.

函数堆栈调用过程

从内存的角度详细的分析C语言中的函数调用过程: 首先写一个测试用的代码: #include <stdio.h> int add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int a = 1, b = 2; int c = 0; c = add(a, b); return 0; } 这是一个简单的的求和函数. 其次,让我们确定一下,程序是从哪里开始运行的: 调试程序,按一下F10(博主用的VS2013), 进入m

函数的调用原理——栈桢

函数调用过程------栈桢 例:剖析"比较两个数之间的大小关系,并把较大数返回"的调用原理: int Max(int x, int y) {                  int z = 0;                  if (x > y)                                 z = x;                  else                                 z = y;