Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >函数调用时堆栈的变化情况

函数调用时堆栈的变化情况

作者头像
恋喵大鲤鱼
发布于 2018-08-03 09:08:31
发布于 2018-08-03 09:08:31
82000
代码可运行
举报
文章被收录于专栏:C/C++基础C/C++基础
运行总次数:0
代码可运行

代码编译运行环境:VS2012+Debug+Win32


函数的正常运行必然要利用堆栈,至少,函数的返回地址是保存在堆栈上的。函数一般要利用参数,而且内部也会用到局部变量,在对表达式进行求值时,编译器还会生成一些无名临时对象,这些对象都是存放在堆栈上的。

下面以Visual C++编译器为例进行研究,考察如下程序。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>

int mixAdd(int i,char c){
    int tmpi=i;
    char tmpc=c;
    return tmpi+tmpc;
}

int main()
{
    int res=mixAdd(4,'A');
    printf("%c",res);
}

在VS2012环境下,以C/C++默认的函数调用约定__cdecl来生成该程序的调试版本(Debug)的汇编代码。

mixAdd()函数对应的汇编代码是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int mixAdd(int i,char c){
00F713E0  push        ebp  
00F713E1  mov         ebp,esp  
00F713E3  sub         esp,0D8h  
00F713E9  push        ebx  
00F713EA  push        esi  
00F713EB  push        edi  
00F713EC  lea         edi,[ebp-0D8h]  
00F713F2  mov         ecx,36h  
00F713F7  mov         eax,0CCCCCCCCh  
00F713FC  rep stos    dword ptr es:[edi]  
    int tmpi=i;
00F713FE  mov         eax,dword ptr [i]  
00F71401  mov         dword ptr [tmpi],eax  
    char tmpc=c;
00F71404  mov         al,byte ptr [c]  
00F71407  mov         byte ptr [tmpc],al  
    return tmpi+tmpc;
00F7140A  movsx       eax,byte ptr [tmpc]  
00F7140E  add         eax,dword ptr [tmpi]  
}
001E1411  pop         edi  
001E1412  pop         esi  
001E1413  pop         ebx  
001E1414  mov         esp,ebp  
001E1416  pop         ebp  
001E1417  ret  

main()函数对应的汇编代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
001E1430  push        ebp  
001E1431  mov         ebp,esp  
001E1433  sub         esp,0CCh  
001E1439  push        ebx  
001E143A  push        esi  
001E143B  push        edi  
001E143C  lea         edi,[ebp-0CCh]  
001E1442  mov         ecx,33h  
001E1447  mov         eax,0CCCCCCCCh  
001E144C  rep stos    dword ptr es:[edi]  
    int res=mixAdd(4,'A');
001E144E  push        41h  
001E1450  push        4  
001E1452  call        mixAdd (01E1168h)  
001E1457  add         esp,8  
001E145A  mov         dword ptr [res],eax  
    printf("%c",res);
001E145D  mov         esi,esp  
001E145F  mov         eax,dword ptr [res]  
001E1462  push        eax  
001E1463  push        1E5858h  
001E1468  call        dword ptr ds:[1E92C0h]  
001E146E  add         esp,8  
001E1471  cmp         esi,esp  
001E1473  call        __RTC_CheckEsp (01E1136h)  
}
001E1478  xor         eax,eax  
}
001E147A  pop         edi  
001E147B  pop         esi  
001E147C  pop         ebx  
001E147D  add         esp,0CCh  
001E1483  cmp         ebp,esp  
001E1485  call        __RTC_CheckEsp (01E1136h)  
001E148A  mov         esp,ebp  
001E148C  pop         ebp  
001E148D  ret  

1.mixAdd()函数汇编代码详解

在进入mixAdd后,可以马上看到这样三条汇编指令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
push ebp      //保留主调函数的帧指针
mov ebp,esp   //建立本函数的帧指针
sub esp,xxx   //为函数局部变量分配空间

这是所有C/C++函数的汇编代码所共同遵循的规范。其中,ebp被称为“帧指针”,扩展基址指针寄存器(extended base pointer),其存放一个指针,该指针指向系统栈最上面一个栈帧的底部。这里的帧指的是每一个函数在被调用时所占有的内存空间,该空间内存放函数的局部数据。

一帧的数据的起始位置由帧指针ebp指明,而帧的另一端由栈指针esp动态维护。ESP就是当前函数的栈顶指针。在函数运行期间,帧指针ebp的值保持不变。

内存管理中,与栈对应是堆。对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方式是向下的,是向着内存地址减小的方向增长。在内存中,“堆”和“栈”共用全部的自由空间,只不过各自的起始地址和增长方向不同,它们之间并没有一个固定的界限,如果在运行时,“堆”和 “栈”增长到发生了相互覆盖时,称为“栈堆冲突”,程序将会崩溃。

在Debug模式下,一个C/C++函数即使没有定义一个局部变量,仍然会分配192Bytes空间,供临时变量使用。如果定义了局部变量,则会为每个局部变量分配12字节的空间(大于任何基本数据类型)。mixAdd()函数中定义了两个局部变量,所以给局部变量和临时变量预留空间大小是192+12+12=216(D8h)。

接下来的汇编指令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
00F713E9  push        ebx  //保存扩展基址寄存器,入栈
00F713EA  push        esi  //保存扩展源变址寄存器,入栈
00F713EB  push        edi  //保存扩展目的变址寄存器,入栈

以上汇编指令保存本函数可能改变的几个寄存器的值,这些寄存器在函数结束后恢复到进入本函数的时候的值。

接下来的汇编指令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
00F713EC  lea         edi,[ebp-0D8h]      //获取栈顶地址
00F713F2  mov         ecx,36h             //赋36H至扩展计数寄存器
00F713F7  mov         eax,0CCCCCCCCh      //给扩展累加寄存器赋值
00F713FC  rep stos    dword ptr es:[edi]  //作用见下面解释

stos指令:字符串存储指令,将eax中的值拷贝至es:[edi]指向的空间,如果设置了direction flag, 那么edi会在该指令执行后减小, 如果没有设置direction flag, 那么edi的值会增加, 这是为了下一次的存储做准备。

rep指令:重复指令,重复执行后面制定的指令操作,重复次数由计数寄存器ecx决定。

因此,上面四条指令的作用是从栈的低地址到高地址将所有的预留空间填满0cccccccch,这样也解释了未赋值的局部变量默认被设置为CCCCCCCCH。

接下来的汇编指令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    int tmpi=i;
00F713FE  mov         eax,dword ptr [i]     //i赋值给eax
00F71401  mov         dword ptr [tmpi],eax  //eax赋值给tmpi
    char tmpc=c;
00F71404  mov         al,byte ptr [c]    //c赋值给寄存器ax低8位al
00F71407  mov         byte ptr [tmpc],al //al赋值给tmpc
    return tmpi+tmpc;
00F7140A  movsx       eax,byte ptr [tmpc]  //带符号扩展传送指令,将rmpc赋值给eax
00F7140E  add         eax,dword ptr [tmpi] //tmpi与eax相加

以下汇编指令,用于函数结束的清理工作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
001E1411  pop         edi     //edi出栈,还原edi
001E1412  pop         esi     // esi出栈,还原esi
001E1413  pop         ebx     // ebx出栈,还原ebx
001E1414  mov         esp,ebp // 清空栈,释放局部变量
001E1416  pop         ebp     //源ebp出栈,恢复ebp
001E1417  ret                 //子程序的返回指令,结束函数

注意:以上汇编代码对mixAdd()函数的调用采用的函数调用约定是__cdecl,这是C/C++程序的默认函数调用约定,其重要的一点就是在被调用函数 (Callee) 返回后,由调用方 (Caller)调整堆栈,因此在main()函数中调用mixAdd()的地方会出现add esp 8这条指令。esp加上8,是因为main()函数将两个参数压入栈,用于传给mixAdd()。感兴趣的读者将mixAdd()函数的定义改为如下形式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int __stdcall mixAdd(int i,char c){
    int tmpi=i;
    char tmpc=c;
    return tmpi+tmpc;
}

即将mixAdd()函数的调用约定改为标准调用约定,那么mixAdd()函数结束时的汇编代码会变成ret 8,main()函数调用mixAdd()的地方会原本出现的add esp 8这条指令将会消失,这是因为__stdcall约定被调函数自身清理堆栈。有关函数调用约定的介绍见我的另一篇blog:关于函数参数入栈的思考

2. main()函数对应的汇编代码注意要点

main()函数的汇编代码大致与mixAdd()相似,但也有不同之处,需要注意一下几点。 printf(“%c”,res);对应的几条汇编代码 (1)printf()函数参数的入栈和调用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
push        1E5858h  //将”%c”入栈
call        dword ptr ds:[1E92C0h]  //调用printf()函数

(2)以下两条汇编代码的意思

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
001E1471  cmp         esi,esp  
001E1473  call        __RTC_CheckEsp (01E1136h)  

上面两条汇编用于表示VC编译器提供了运行时刻的对程序正确性/安全性的一种动态检查,可以在项目属性的C++选项中打开来启用Runtime Check。开启与打开步骤如下图:


参考文献

[1] http://www.cnblogs.com/awpatp/archive/2012/08/05/2623628.html [2]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008.[3.3(P97-P100)]

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015年10月22日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
「神经常微分方程」提出者之一David Duvenaud:如何利用深度微分方程模型处理连续时间动态
提到 David Duvenaud 你或许有些陌生,但最近大热的「神经常微分方程」想必你一定听说过。
机器之心
2020/07/06
1.1K0
AI攻破高数核心,1秒内精确求解微分方程、不定积分,性能远超Matlab
这是Facebook发表的新模型,1秒给出的答案,超越了Mathematica和Matlab这两只付费数学软件30秒的成绩。
量子位
2019/12/20
1K0
AI攻破高数核心,1秒内精确求解微分方程、不定积分,性能远超Matlab
使用自变分原理改进正则化核回归:通过变分法推导和推广Nadaraya-Watson估计
核回归技术是一组非参数方法,用于通过一组数据点拟合平滑的曲线。Nadaraya-Watson 估计就是这样一种方法。它通常是在自变量分布的核密度估计以及因变量和自变量联合分布的基础上,通过计算因变量的条件期望得到的。
deephub
2021/12/09
1K0
使用自变分原理改进正则化核回归:通过变分法推导和推广Nadaraya-Watson估计
高数期末有救了?AI新方法解决高数问题,性能超越Matlab
机器学习的传统是将基于规则的推断和统计学习对立起来,很明显,神经网络站在统计学习那一边。神经网络在统计模式识别中效果显著,目前在计算机视觉、语音识别、自然语言处理等领域中的大量问题上取得了当前最优性能。但是,神经网络在符号计算方面取得的成果并不多:目前,如何结合符号推理和连续表征成为机器学习面临的挑战之一。
机器之心
2019/12/24
1.6K0
【数值计算方法(黄明游)】常微分方程初值问题的数值积分法:欧拉方法(向后Euler)【理论到程序】
是一个关键参数,它决定了离散化的程度,选择合适的步长对于数值解的准确性和稳定性非常重要。
Qomolangma
2024/07/30
3230
【数值计算方法(黄明游)】常微分方程初值问题的数值积分法:欧拉方法(向后Euler)【理论到程序】
加州理工华人博士提出傅里叶神经算子,偏微分方程提速1000倍,告别超算!
微分方程是数学中重要的一课。所谓微分方程,就是含有未知函数的导数。一般凡是表示未知函数、未知函数的导数与自变量之间关系的方程,就叫做微分方程。
新智元
2021/11/15
1.2K0
【GAN优化】从动力学视角看GAN是一种什么感觉?
今天讲述的内容是GAN与动力学,这是一个非常好玩、非常新鲜的视角。考虑到很多人微积分和线性代数等知识的涉猎不多,我将会对涉及的内容都做出基本说明,也并不会涉及过深入的东西,然后争取串成一个故事,扩展一下大家的视野。
用户1508658
2019/08/29
1.5K1
Matlab系列之符号运算(下)
上一篇主要对符号对象进行了一些生成和使用的基本操作,然后本篇将介绍符号矩阵、微积分、积分变换以及符号方程的求解,具体内容就往下慢慢看了。
狂人V
2020/10/10
1.4K0
Matlab系列之符号运算(下)
求解微分方程,用seq2seq就够了,性能远超 Mathematica、Matlab
近日,Facebook AI研究院的Guillaume Lample 和Francois Charton两人在arxiv上发表了一篇论文,标题为《Deep Learning for Symbolic Mathematics》。
AI科技评论
2019/12/19
1.2K0
求解微分方程,用seq2seq就够了,性能远超 Mathematica、Matlab
机器学习会取代数学建模吗?
来源商业新知网,原标题:机器学习会取代数学建模吗?让我们假设一个微积分落后但深度学习发达的文明社会……
商业新知
2019/06/12
1.4K0
机器学习会取代数学建模吗?
【机器学习】因微知著,穷数通灵:微积分与机器学习的量化之美
在机器学习的学习旅程中,微积分不仅是理解单变量变化的工具,更是处理多变量和复杂系统的关键。上一篇文章中,我们详细讲解了积分的基本概念与计算方法,并通过实战项目展示了积分在概率与统计中的应用。本篇文章将进一步探讨多重积分与微分方程,这两者在机器学习中的应用广泛且重要。通过理论与实践相结合的方式,你将能够更好地理解和运用这些高级微积分概念。
半截诗
2025/01/09
2450
【机器学习】因微知著,穷数通灵:微积分与机器学习的量化之美
Matlab符号运算
sym函数用于建立单个符号对象,其常用调用格式为:符号对象名=sym(A) 将由A来建立符号对象。其中,A可以是一个数值常量、数值矩阵或数值表达式(不加单引号),此时符号对象为一个符号常量;A也可以是一个变量名(加单引号),这是符号对象为一个符号常量。
十二惊惶
2024/02/28
2360
硬核NeruIPS 2018最佳论文,一个神经了的常微分方程
在最近结束的 NeruIPS 2018 中,来自多伦多大学的陈天琦等研究者成为最佳论文的获得者。他们提出了一种名为神经常微分方程的模型,这是新一类的深度神经网络。神经常微分方程不拘于对已有架构的修修补补,它完全从另外一个角度考虑如何以连续的方式借助神经网络对数据建模。在陈天琦的讲解下,机器之心将向各位读者介绍这一令人兴奋的神经网络新家族。
机器之心
2019/01/02
1K0
用Python来计算
最近做毕业设计的时候需要计算机器人运动学和动力学,但是这玩意在计算过程中需要计算很多矩阵,这个就很头疼。一般这种计算直接给MATLAB,但是它太太太大了,界面也太太太丑了,而且一运行就会很卡,再加上MATLAB也很贵,因此用了python的sympy库。用了之后发现这个是一个宝库,遇到问题,别问,问就python因此写一下。
用户6948990
2025/04/03
1000
用Python来计算
有限元法(FEM)
空间和时间相关问题的物理定律通常用偏微分方程(PDE)来描述。对于绝大多数的几何结构和所面对的问题来说,可能无法求出这些偏微分方程的解析解。不过,在通常的情况下,可以根据不同的离散化 类型来构造出近似的方程,得出与这些偏微分方程近似的数值模型方程,并可以用数值方法求解。如此,这些数值模型方程的解就是相应的偏微分方程真实解的近似解。有限元法(FEM)就是用来计算出这些近似解的。
技术客
2022/05/19
2K0
热传导方程非特征 Cauchy 问题的一些笔记
1、解的存在性: \forall y \in Y, \exist x \in X, 使得 Ax=y. 2、解的唯一性: \forall y_1, y_2 \in Y, y_1 \neq y_2, 有 Ax_1=y_1, Ax_2=y_2, 使得 x_1 \neq x_2. 3、解的稳定性(即解的连续性):若有 Ax_1=y_1, Ax_2=y_2, 则当 y_1 \rightarrow y_2 时, 使得 x_1 \rightarrow x_2.
Cloud-Cloudys
2023/10/21
6210
热传导方程非特征 Cauchy 问题的一些笔记
有哪些不定积分的运算(心算)技巧?[1]
计算不定积分实际上就是根据导函数找原函数。求导的计算方法有一定的套路,对于任给的初等函数都套这些求导法则都可以找到导函数。但是不定积分不然。不定积分的两种运算律——换元积分法和分部积分法——都只是告诉你你可以怎么算,但是并没说这么算一定能算出来。因此,不定积分的计算有十分强的技巧性。
云深无际
2021/04/14
1.9K0
有哪些不定积分的运算(心算)技巧?[1]
有限元法在非线性偏微分方程中的应用
Mathematica 12 为偏微分方程(PDE)的符号和数值求解提供了强大的功能。本文将重点介绍版本12中全新推出的基于有限元方法(FEM)的非线性PDE求解器。首先简要回顾用于求解 PDE 的 Wolfram 语言基本语法,包括如何指定狄利克雷和诺伊曼边界条件;随后我们将通过一个具体的非线性问题,说明 Mathematica 12的 FEM 求解过程。最后,我们将展示一些物理和化学实例,如Gray-Scott模型和与时间相关的纳维-斯托克斯方程。更多信息可以在 Wolfram 语言教程"有限元编程"中找到,本文大部分内容都以此为基础(教程链接见文末)。
WolframChina
2020/06/24
2.7K0
利用python的sympy求解微积分
一般的数学算式math就可以解决了,但是涉及到极限,微积分等知识,math就不行了,程序中无法用符号表示出来。
叶子陪你玩
2020/03/12
1.7K0
在 COMSOL 中模拟瞬态加热的方法
COMSOL Multiphysics®软件经常被用来模拟固体的瞬态加热。瞬态加热模型很容易建立和求解,但它们在求解时也不是没有困难。例如,对瞬态加热结果的插值甚至会使高级 COMSOL®用户感到困惑。在这篇文章中,我们将探讨一个简单的瞬态加热问题的模型,并利用它来深入了解这些细微差别。
CAE学习笔记
2023/02/13
2.3K0
在 COMSOL 中模拟瞬态加热的方法
推荐阅读
相关推荐
「神经常微分方程」提出者之一David Duvenaud:如何利用深度微分方程模型处理连续时间动态
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验