从内存的角度详细的分析C语言中的函数调用过程:
首先写一个测试用的代码:
#include <stdio.h> int add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int a = 0, b = 0; int c = 0; c = add(a, b); return 0; }这是一个简单的的求和函数。
其次,让我们确定一下,程序是从哪里开始运行的:
调试程序,按一下F10(博主用的VS2013),
进入main函数:
然后进调试--->窗口--->调用堆栈(用来显示函数的调用关系)。
发现正在调用main这个函数,现在我想知道是谁在调用main函数,F10一路走到return 0,接着换F11,逐语句调试,然后会发现,main函数返回后,我们来到了这里:
再看看此时的调用堆栈:
直接来看,现在运行的函数是__tmainCRTStartup(),这个函数又被mainCRTStratup()调用,而我们刚刚是从main()函数返回来的,所以,main()函数是由__tmainCRTStartup()这个函数调用的。
了解了main()函数是被谁调用后,我们可以进一步分析这其中的细节了!
现在重新F10进入调试,到这一步:
进入main()函数后还没有执行任何一条语句,我们 右击-->转到反汇编:
看到了汇编语言的代码,图中的ebp和esp是什么东西呢?我们知道,调用函数的时候操作系统要给这个函数分配一段内存空间,之前又说了main()函数是由—__tCRTStartup()函数调用的,所以请看:
mainCRTStratup()函数调用__tmainCRTStra()函数的时候就会从栈上为__tmainCRTStra()分配类似图中这么一块空间。我们知道栈是由高地址向低地址扩展的。其中ebp叫做栈底指针,esp叫做栈顶指针(当然也有其它叫法)。ebp,esp本身是一个寄存器,其中存放了地址时,我们就称之为指针!
现在再来看汇编程序:
按一下F10执行第一条语句,箭头指向下一条语句,变成这样:
(和我们再外边的调试是一样的)这句 push ebp 就是将ebp中的值进行压栈,而此时ebp存放的是系统分给__tmainCRTStartup()函数的空间的起始地址。因为我们现在要调用main()函数了,所以当然要先把__tmainCRTStartup()函数的运行状态保存下来,这样main()函数才能返回的时候才能找得到!push是在栈顶进行的,所以,push之后,esp要向上移动:
(为了方便字体显示我将图中的方块拉开了一些,与之前的表示并没有什么不同)
然后继续执行下一条语句: mov ebp,esp
即把esp的值赋给ebp,这样,ebp也就指向了现在esp的位置,如下图:
接着又执行语句:sub esp,0E4h
即将esp的值减去E4h,所以esp向上移动了E4h个位置(相当于申请了这么大的一块空间),如下:
新申请的这块空间就给main()用了。
接下来紧接着三条push语句将下边要用到的寄存器中原来的值存储起来,等我们借用完寄存器后再给人家pop回去,不管它,这里esp再向上移动三次。
下边四条语句共同完成一个任务,就是将图中的正方形区域初始化为0CCCCCCCh(你经常看到的:烫烫烫烫......)
然后是:lea edi,[ebp-0E4h]
就是将ebp减去E4h的值赋给edi,这个E4h是不是很眼熟呢?它就是我们上一步分配给main()的空间的大小,即edi指向前一副图esp的位置;
接着:mov ecx,39h
把39h放在ecx中(充当了计数器)
接着:mov eax,0CCCCCCCCh
把要初始化的数据写入eax
最后:rep stos dword ptr es:[edi]
循环的从低地址(ebp-0E4h)向高地址(ebp)写0CCCCCCCCh,循环了39h次!
我们转到内存中看一下:
先查找ebp
相应的位置已经被初始化为0CCCCCCCh,其它部分是乱码(此时ebp值为0x009EF860,它之上的空间是分配给main()的)
程序继续往下执行:
//这下面本来也是有很多的配图的,不知道什么原因显示不出来。。。我的图是动态变化的,没精力在从头搞一次了,大家有兴趣的话自己转到反汇编调试调试
//很容易理解的。不好意思了!
创建了两个变量,初始化为0:
接着创建c:
此时我们的内存分配变成了这样:
(底下多余的部分由于空间问题先不显示出来)
然后到了这里:
把b的值赋给eax,然后eax压栈
把a的值赋给ecx,然后ecx压栈,变成这样:
再然后就要调用add()函数了:
调用call的时候,程序会自动的将当前程序的下一条语句的地址压栈,以便调用完后返回,继续F11跟进去:
这下进入了add函数,这几句和之前分析的差不多,为add()开辟了一块空间,然后初始化,ebp,esp指向相应的位置。
然后创建变量z,将x的值放入eax,y的值加到eax里,又赋给了z,将z的值返回的时候当然不能把它留在z里,因为z是个局部变量,函数返回后就销毁了。所以将z值放入eax。
连续三个pop,然后把ebp的值赋给了esp!注意:esp是栈顶指针,让栈顶指针指向了栈底,又pop ebp,即把main()函数空间的ebp给了现在的ebp,所做的工作相当于释放了add()调用时产生的空间(实际上仍然在,只是不属于你)。最后ret(与之前的call配合使用),程序运行到之前call的下一条语句:
把eax的值赋给了变量z,然后eax异或eax即为清零。
接下来又是一样:
连续3个pop,释放main()函数的空间,又调用了一个__RTC_CheckEsp(0D4113Bh),我们不管它,F10下去,ret,来到了之前看到的调用main()的函数:
一路F10,程序运行完毕(依次回到__tCRTStartup()函数和CRTStartup()函数,有兴趣的话可以跟进去看看)。
从内存角度看C函数的调用过程
时间: 2024-10-06 06:38:49
从内存角度看C函数的调用过程的相关文章
内存角度看c#中值类型和引用类型的区别(转)
1. 值类型的数据存储在内存的栈中:引用类型的数据存储在内存的堆中,而内存单元中只存放堆中对象的地址. 2. 值类型存取速度快,引用类型存取速度慢. 3. 值类型表示实际数据,引用类型表示指向存储在内存堆中的数据的指针或引用 4. 值类型继承自System.ValueType,引用类型继承自System.Object 5. 栈的内存分配是自动释放:而堆在.NET中会有GC来释放 6. 值类型的变量直接存放实际的数据,而引用类型的变量存放的则是数据的
使用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 动态链接库函数的调用过程. (本文同步更
函数的调用过程(栈帧)
1.什么是栈帧? 栈帧也叫过程活动记录,是编译器用来实现函数调用过程的一种数据结构.C语言中,每个栈帧对应着一个未运行完的函数.从逻辑上讲,栈帧就是一个函数执行的环境:函数调用框架.函数参数.函数的局部变量.函数执行完后返回到哪里等等.栈是从高地址向低地址延伸的.每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息.寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址). 2.Add()函数的调用过程 我们以Add()函数为例深入的研究一
手动跟踪函数的调用过程【转】
转自:https://blog.csdn.net/ccjjnn19890720/article/details/6871036/ 今天是10月13号,不知道为什么日子过的如此的快,大概是假期的原因吧.在十一国庆以后,上了3天课又放假了...感觉研究生的生活越来越没有学生样子啦...老师在很久以前就安排了一个任务给我,叫我完成在arm板子上的视频显示,做过了前期的JPEG的显示,觉得这个问题本身不是很大.大概是自己对这种事情了解的太少,当真正的去接触的时候就觉得难度很大. 视频本身是有一帧一帧的数
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;
函数的调用过程——栈帧。
今天我们来看一下函数的调用过程与栈帧. 我们通过一段简单的代码和图示来介绍这个过程: #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:程序计数器——保存程序当前正在执行指令的下一个指令的地址 接着我们以下面这段代码为例,深入到函数的调用过程中去: #
函数递归调用过程中的调用堆栈的情况
为了加深对函数递归调用过程中的理解,本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 //阶乘的实
函数堆栈调用过程
从内存的角度详细的分析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