C语言函数调用堆栈详情分析

目录
  • 一、C函数栈帧开辟以及回退过程
  • 二、C函数调用约定和返回值

一、C函数栈帧开辟以及回退过程

__cdecl(C语言默认调用方式,函数参数8字节以内,使用push。本节采用此方式)

main函数的栈帧调用sum函数的栈帧,sum函数栈帧使用完了以后回退都是怎么进行的,要搞清楚这个问题必须得看汇编代码,汇编代码分为两种:inter x86(windows)和AT&T(unix)。这两种汇编非常相似,x86的汇编是从右向左看,unix的汇编是从左向右看的。

局部变量都是通过栈底指针ebp偏移访问,不生成符号,不属于数据,属于指令。

形参压栈在C/C++中是从右向左压栈,因为要支持可变长参数,如果从左向右,编译器就不知道用户传入了多少实参,形参内存是在调用函数栈帧中开辟,每压栈一个实参,都会开辟一个形参的空间,栈顶指针esp都会减4字节。

实参压栈完成后需要调用call指令来执行sum函数,执行完sum函数后,执行完sum函数后需要回到调用指令(call)的下一条指令继续执行。

call指令做两件事:

  • 1.把下一行指令的地址入栈
  • 2.jmp跳转

栈空间图:

执行call指令后,程序调到这里,不是sum函数的指令部分

在编译阶段,所有汇编指令代码引用符号的地方全部不是合法的地址(因为我们当前文件可能引用外部的符号,而编译阶段是独立编译的,我们链接的时候才会进行符号解析、合并符号表等操作,之后再给符号分配内存地址),对于数据符号来说是零地址,对于函数符号来说是-4。那么当我们在链接阶段符号解析完成以后,得到每一个符号的具体地址,给数据符号分配的地址是绝对地址,给函数符号分配的地址是与下一行指令地址的一个偏移量,这样当程序需要跳转到某个函数地址的时候,取出PC寄存器保存的地址与该偏移量相加就得到函数的入口地址。

计算相对偏移量后就进入了sum函数,先执行下面一段指令,才执行我们写的sum函数

每次执行一个函数前要执行三个操作:

  • 把调用方的栈底地址入栈(push ebp),让ebp指针指向当前函数的栈底(mov ebp,esp)
  • 移动栈顶指针esp,给被调用函数开辟栈帧(sub esp 44h)
  • 初始化新栈帧内存,把esp和ebp之间所有的栈内存全部初始化成0xCCCCCCCC(rep stos dword ptr [edi])无效值。

Linux为栈帧不分配初始值,windows会分配初始值,为0xCCCCCCCC(-858993460)

然后执行sum函数的指令:

局部变量通过栈底指针ebp负向偏移访问,形参通过ebp正向偏移访问,eax为a+b的计算结果,将计算结果赋值给temp,return的时候将temp的值赋值给eax,给调用方返回

栈帧清退:

可以看到,开辟栈帧的时候我们对占内存进行了初始化,但是栈帧清退的时候仅仅就是修改了esp和ebp,没有做其他任何操作,如果我们此时通过一些手段去访问已被清退栈帧的内存,还是可以访问到的,因为数据还存在

参数清除:

sum函数执行完成后,从PC寄存器中取出地址继续执行,这里PC寄存器存放的是call指令的下一行地址,这条指令做的操作是回退形参变量占的内存,形参内存由调用方开辟和释放。sum函数返回值由eax寄存器带回来。

在被调用方执行完成后,通过pop ebp就知道应该回到哪(恢复栈底),再通过ret指令就知道回到哪以后从哪一行指令开始运行(取出栈顶元素放到PC寄存器里面,而栈顶元素存的就是call的下一行地址)
sum函数栈底保存的是main的栈底地址,main函数栈底保存的是调用main的函数的栈底地址

二、C函数调用约定和返回值

函数调用约定和返回值

到此这篇关于C语言函数调用堆栈详情分析的文章就介绍到这了,更多相关C函数调用堆栈内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript调用堆栈及setTimeout使用方法深入剖析

    Javascript中会经常用到setTimeout来推迟一个函数的执行,如: 复制代码 代码如下: setTimeout(function(){alert("Hello World");},1000); 会在执行到这句话后延迟1秒钟来弹出alert窗口.那么再看这一段: 复制代码 代码如下: function a(){ setTimeout(function() {alert(1)}, 0); alert(2); } a(); 注意这段代码中的setTimeout延迟设为了0,就是延

  • 详解JavaScript中的执行上下文及调用堆栈

    一.执行上下文是什么 代码运行是在一定的环境之中运行的,这个运行环境我们就成为执行环境,也就是执行上下文,按照执行环境不同,我们可以分为三类: 全局执行环境:代码首次执行时候的默认环境 函数执行环境:每当执行流程进入到一个函数体内部的时候 Eval执行环境:当eval函数内部的文本执行的时候 二.执行上下文栈是什么 既然是'栈',那就得符合'栈'的特性,即数据结构是先进后出.下面我们看一段代码: function cat(a){ if(a<0){ return false; } console.

  • JavaScript实现显示函数调用堆栈的方法

    本文实例讲述了JavaScript实现显示函数调用堆栈的方法.分享给大家供大家参考,具体如下: 许多大型的JavaScript应用程序间的函数调用关系是非常复杂的,在开发或者调试过程中,经常需要跟踪某个函数是由哪些函数调用后才触发执行的,弄清楚这些函数的调用顺序对我们理解代码的数据流向是非常重要的. Firebug提供了console.trace()来显示函数堆栈,在需要调试的地方加上下面的一行代码就能显示该函数调用时的上下文关系.IE6就没有这么方便了,它没有提供显示函数堆栈的工具,当不可避免

  • JavaScript 对引擎、运行时、调用堆栈的概述理解

     随着JavaScript越来越流行,越来越多的团队广泛的把JavaScript应用到前端.后台.hybrid 应用.嵌入式等等领域. 这篇文章旨在深入挖掘JavaScript,以及向大家解释JavaScript是如何工作的.我们通过了解它的底层构建以及它是怎么发挥作用的,可以帮助我们写出更好的代码与应用.据 GitHut 统计显示,JavaScript 长期占据GitHub中 Active Repositories 和 Total Pushes 的榜首,并且在其他的类别中也不会落后太多. 如果

  • C语言函数调用堆栈详情分析

    目录 一.C函数栈帧开辟以及回退过程 二.C函数调用约定和返回值 一.C函数栈帧开辟以及回退过程 __cdecl(C语言默认调用方式,函数参数8字节以内,使用push.本节采用此方式) main函数的栈帧调用sum函数的栈帧,sum函数栈帧使用完了以后回退都是怎么进行的,要搞清楚这个问题必须得看汇编代码,汇编代码分为两种:inter x86(windows)和AT&T(unix).这两种汇编非常相似,x86的汇编是从右向左看,unix的汇编是从左向右看的. 局部变量都是通过栈底指针ebp偏移访问

  • C语言函数调用约定和返回值详情

    目录 一.函数调用约定 1. 影响函数生成的符号名 2. 影响形参内存的释放者 _stdcall _fastcall _thiscall 二.函数的返回值 1. 0 < 返回值 <= 4字节 2. 4字节 < 返回值 <= 8字节 3. 返回值 > 8字节 一.函数调用约定 _cdecl:C调用约定 _stdcall:Windows标准的调用约定 _fastcall:快速调用约定 _thiscall:C++的成员函数调用约定 以上的函数调用约定入参都是从右向左,只有PASCA

  • 深入浅析C语言中堆栈和队列

    1.堆和栈 (1)数据结构的堆和栈 堆栈是两种数据结构. 栈(栈像装数据的桶或箱子):是一种具有后进先出性质的数据结构,也就是说后存放的先取,先存放的后取.这就如同要取出放在箱子里面底下的东西(放入的比较早的物体),首先要移开压在它上面的物体(放入的比较晚的物体). 堆(堆像一棵倒过来的树):是一种经过排序的树形数据结构,每个结点都有一个值.通常所说的堆的数据结构,是指二叉堆.堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆.由于堆的这个特性,常用来实现优先队列,堆的存取是随意,

  • C语言动态内存管理分析总结

    目录 什么是动态内存分配 动态内存函数的介绍 free malloc calloc realloc 动态内存管理中常见的错误 对NULL指针的解引用操作 对动态开辟空间的越界访问 对非动态开辟内存使用free释放 使用free释放一块动态开辟内存的一部分 对同一块动态内存多次释放 动态开辟内存忘记释放(内存泄漏) 一些经典的笔试题 题目1 题目2 题目3 题目4 柔性数组 柔性数组的特点 柔性数组的优势 什么是动态内存分配 我们都知道在C语言中,定义变量的时候,系统就会为这个变量分配内存空间,而

  • Go语言流程控制详情

    目录 1.流程控制 2.if 语句 3.goto 4.for语句 5.switch 1.流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑. 流程控制包含分三大类:条件判断,循环控制和无条件跳转. 2.if 语句 if 也许是各种编程语言中最常见的了,它的语法概括起来就是: 如果满足条件就做某事,否则做另一件事. Go 里面 if 条件判断语句中不需要括号,如下代码所示: if x > 10 {     fmt.Println("x i

  • 浅谈C语言函数调用参数压栈的相关问题

    参数入栈的顺序 以前在面试中被人问到这样的问题,函数调用的时候,参数入栈的顺序是从左向右,还是从右向左.参数的入栈顺序主要看调用方式,一般来说,__cdecl 和__stdcall 都是参数从右到左入栈. 看下面的代码: #include <stdio.h> int test(int a, int b) { printf("address of a %x.\n", &a); printf("address of b %x.\n", &b)

  • R语言绘制散点图实例分析

    散点图显示在笛卡尔平面中绘制的许多点. 每个点表示两个变量的值. 在水平轴上选择一个变量,在垂直轴上选择另一个变量. 使用plot()函数创建简单散点图. 语法 在R语言中创建散点图的基本语法是 - plot(x, y, main, xlab, ylab, xlim, ylim, axes) 以下是所使用的参数的描述 - x是其值为水平坐标的数据集. y是其值是垂直坐标的数据集. main要是图形的图块. xlab是水平轴上的标签. ylab是垂直轴上的标签. xlim是用于绘图的x的值的极限.

  • R语言关于协方差分析实例分析

    我们使用回归分析创建模型,描述变量在预测变量对响应变量的影响. 有时,如果我们有一个类别变量,如Yes / No或Male / Female等.简单的回归分析为分类变量的每个值提供多个结果. 在这种情况下,我们可以通过将分类变量与预测变量一起使用并比较分类变量的每个级别的回归线来研究分类变量的效果. 这样的分析被称为协方差分析,也称为ANCOVA. 例 考虑在数据集mtcars中内置的R语言. 在其中我们观察到字段"am"表示传输的类型(自动或手动). 它是值为0和1的分类变量.汽车的

  • Go语言中的逃逸分析究竟是什么?

    目录 1.逃逸分析介绍 2.Go中内存分配在哪里? 3.Go与C++内存分配的区别 4.逃逸分析骚操作 5.逃逸分析引申示例说明 1.逃逸分析介绍 学计算机的同学都知道,在编译原理中,分析指针动态范围的方法称之为逃逸分析.通俗来讲,当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了"逃逸". Go语言的逃逸分析是编译器执行静态代码分析后,对内存管理进行的优化和简化,它可以决定一个变量是分配到堆还栈上. 写过C/C++的小伙伴应该知道,使用比较经典的malloc和new函数可以

随机推荐