C开发基础--函数调用栈
发现有一些问题几乎是所有的新人都会遇到,而且也常因为缺乏一些基本的知识而无从下手。函数调用栈的内容就是其中之一。于是花点时间把以前写的内容整理出来。
程序在运行期间,内存中有一块区域,用来实现程序的函数调用机制。这块区域是一块LIFO的数据结构区域,我们可以叫函数栈(调用栈)。每个未退出的函数都会在函数栈中拥有一块数据区,我们叫函数的栈帧。函数的调用栈帧中,保存了相应的函数的一些重要信息:函数中使用的局部变量,函数的参数,另外还有一些维护函数栈所需要的数据,比如EBP指针,函数的返回地址。如下图。我们假设程序当前执行的函数是Z函数,那么在函数调用栈中就会存在类似像这样的结构(EBP所指向的其实是“父函数”的调用栈帧,如何做到的后面会解释):

编译器把C/C++代码编译成汇编指令时,会生成一系列(很有规则)的指令来支持函数调用的机制。当一个函数调用发生时(我们假设是Z函数内调用了A函数):
会执行零到多个PUSH指令(用于参数入栈),然后有执行一个CALL指令。CALL指令内部其实还暗含了一个将返回地址(即CALL指令下一条指令的地址)压栈的动作。之后,IP(instruction point)指向要跳转的指令的地址(CALL指令的目标地址,也就是下一个函数)
大部分的本地编译器都会在每个函数体之前插入类似如下指令:PUSH EBP; MOV EBP ESP;即,在程式执行到一个函数的真正函数体时,已有以下数据顺序入栈:参数,返回地址,EBP。(注意,EBP是如何作到指向上个函数调用栈帧的)
将栈顶指针进行上移,“空”出一块区域,用于临时地存放函数的局部变量。
这几步完成(也就是一个函数调用发生了),函数调用栈就会变成这个样子,函数调用结束的时候,相应的做“反向的动作”就可以了。

我们用一段非常简单的真实代码看来:
- int increase(int a) {
- int temp = 4;
- return a + 3;
- }
- int main(int argc, char* const argv[])
- {
- int sum = increase(3);
- return 0;
- }
在main函数中调用 increase 函数。用VS单步断点打开汇编模式,可以看到如下的代码
- int sum = increase(3);
- 00D2561E push 3
- 00D25620 call increase (0D2142Eh)
- 00D25625 add esp,4
- 00D25628 mov dword ptr [sum],eax
对照前面的说明,我们可以看到,调用函数前有 push 指令先把函数参数压栈。之后才真正的call increase 。然后我们进入 increase 函数再看看函数体是什么样的。
- int increase(int a) {
- 000455C0 push ebp
- 000455C1 mov ebp,esp
- 000455C3 sub esp,0CCh
- 000455C9 push ebx
- 000455CA push esi
- 000455CB push edi
- 000455CC lea edi,[ebp-0CCh]
- 000455D2 mov ecx,33h
- 000455D7 mov eax,0CCCCCCCCh
- 000455DC rep stos dword ptr es:[edi]
- int temp = 4;
- 000455DE mov dword ptr [temp],4
- return a + temp;
- 000455E5 mov eax,dword ptr [a]
- 000455E8 add eax,dword ptr [temp]
- }
- 000455EB pop edi
- 000455EC pop esi
- 000455ED pop ebx
- 000455EE mov esp,ebp
- 000455F0 pop ebp
- 000455F1 ret
进入函数前,做的动作主要是保存各寄存器,注意“sub esp,0xcch”就是移动ESP,空出局部变量的“位置”,为什么只有一个局部变量,却生成了这么大块区域呢?
Stackoverflow上有解释:
This extra space is generated by the /Zi compile option. Which enables Edit + Continue. The extra space is available for local variables that you might add when you edit code while debugging.
You are also seeing the effect of /RTC, it initializes all local variables to 0xcccccccc so that it is easier to diagnose problems due to forgetting to initialize variables. Of course none of this code is generated in the default Release configuration settings.
从这段简单的代码中,我们可以知道函数调用大概是什么回事了。通过上面的内容,我们仔细体会下ESP和EBP两个寄存器的变化,也就下面向个指令
013D55C0 push ebp // 构建新的调用帧
013D55C1 mov ebp, esp013D55EE mov esp, ebp // 恢复到原来的调用帧
013D55F0 pop ebp
再加上参数,返回地址,局部变量的入栈出栈,通过这样一种统一的、并不复杂代码生成模式和数据结构,可以应对任意复杂的函数调用情况,极其灵活。我一直觉得这是计算机科学中非常漂亮的一个创造,也是以简驭繁的一个经曲例子。
C开发基础--函数调用栈的更多相关文章
- 前端基础进阶(六):在chrome开发者工具中观察函数调用栈、作用域链与闭包
在前端开发中,有一个非常重要的技能,叫做断点调试. 在chrome的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察JavaScript的执行过程,直观感知函数调用栈,作用域链,变量对象, ...
- 在chrome开发者工具中观察函数调用栈、作用域链与闭包
在chrome开发者工具中观察函数调用栈.作用域链与闭包 在chrome的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察JavaScript的执行过程,直观感知函数调用栈,作用域链,变量 ...
- .NET基础拾遗(5)多线程开发基础
Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...
- Android 开发基础及环境配置
2011年买了第一部安卓操作系统的手机,当时势头正盛的HTC不可思议(incredible),当时的想法就是想学习下智能手机开发,但是由于各种原因,客观上是公司的项目太忙了,忙于项目管理.团队建设.客 ...
- Android 中调试手段 打印函数调用栈信息
下面来简单介绍下 android 中的一种调试方法. 在 android 的 app 开发与调试中,经常需要用到打 Log 的方式来查看函数调用点. 这里介绍一种方法来打印当前栈中的函数调用关系 St ...
- Linux Debugging(一): 使用反汇编理解C++程序函数调用栈
拿到CoreDump后,如果看到的地址都是????,那么基本上可以确定,程序的栈被破坏掉了.GDB也是使用函数的调用栈去还原"事故现场"的.因此理解函数调用栈,是使用GDB进行现场 ...
- go语言调度器源代码情景分析之四:函数调用栈
本文是<go调度器源代码情景分析>系列 第一章 预备知识的第3小节. 什么是栈 栈是一种“后进先出”的数据结构,它相当于一个容器,当需要往容器里面添加元素时只能放在最上面的一个元素之上,需 ...
- Python运维开发基础09-函数基础【转】
上节作业回顾 #!/usr/bin/env python3 # -*- coding:utf-8 -*- # author:Mr.chen # 实现简单的shell命令sed的替换功能 import ...
- Struts2开发基础
Struts2开发基础 struts2采用拦截器的机制来处理用户的请求,使得业务逻辑控制器能够与ServletAPI完全脱离开. 1. Hello World! 配置web.xml <?xml ...
随机推荐
- 20145208《Java程序设计》第3周学习总结
20145208 <Java程序设计>第3周学习总结 教材学习内容总结 认识对象 类类型 在第三章的学习中,我了解到JAVA可区分为基本类型和类类型两种类型,在上周的学习中我学习了JAVA ...
- silverlight 双击事件
silverlight的一些功能真让我感到惊奇 就拿双击事件来说一下 写一个简单的例子 <StackPanel x:Name="st_De" MouseLeftButtonD ...
- C#基础之垃圾回收
1.托管资源的回收 我们都知道C#托管资源的回收由GC全权负责控制,可是什么时候GC会回收垃圾呢?一般出现以下情况会回收垃圾:手动调用GC.Collect()强制回收:第0代对象内存已满:应用程序域被 ...
- Ajax基础之封装3
今天接着我们上篇博文的栗子,现在我来扩大一下需求,之前是点击按钮取出新闻,现在要实现每隔一段事件进行新闻的更新.这个时候,肯定是加一个定时器,然后每隔一段事件,再进行一次Ajax请求,既然要经常用到A ...
- 高校手机签到系统——第一部分Authority权限系统(下)
很抱歉,之前寝室光纤断了,所以到现在才更新这个系列的第二篇博客.点击访问高校手机签到系统——第一部分Authority权限系统(上) 这几天我反思了一下上一篇写博上的方式,一味的贴代码式的,是否应该更 ...
- 整合 Bing translator 到自己的系统中
整合这个功能, 是因为 aliexpress 的买家来自不同国家, 我的 "小卖家" 同步到买家的留言, 很多西班牙,俄罗斯等小语种的文字, 看不懂. Google 被墙, 基本很 ...
- php并发请求
一般在php进行请求url的时候,直接用 fopen 函数就可以搞定了,比如像这样: $file=fopen("http://www.cnblogs.com","r&qu ...
- JavaScript事件---事件绑定和深入
发文不易,转载传播,请亲注明链接出处,谢谢! 内容提纲: 1.传统事件绑定的问题 2.W3C事件处理函数 3.IE事件处理函数 4.事件对象的其他内容 事件绑定分为两种:一种是传统事件绑定(内联模型, ...
- 第四十二课:基于CSS的动画引擎
由于低版本浏览器不支持css3 animation,因此我们需要根据浏览器来选择不同的动画引擎.如果浏览器支持css3 animation,那么就使用此动画引擎,如果不支持,就使用javascript ...
- 思甜雅--关于qq的NABCD模型分析
个人连接:http://www.cnblogs.com/xiaoliulang/ 关于QQ的NABCD模型 N--Need 随着电脑的普及,人们在网络上进行交流的时间越来越多,由于现有的交流工具还不是 ...