转自:https://blog.csdn.net/lixiangminghate/article/details/43195717
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
 
int ShowEsp(int* arg1,int* arg2);
 
/*
引言 各种面试宝典上都会说 又说栈在进程空间的高地址部分,向下扩展; 
堆在进程空间的低地址部分,堆向上扩展
来验证一下是否正如所说这些变量在内存中如何分布?
*/
int main()
{
 
    //0)
    int i=0xABCDABCD;
    //int* j = (int*)malloc(sizeof(int)); //malloc为什么被我注掉了?
    //memcpy(j,&i,sizeof(int));
    int* k = (int*)VirtualAlloc(NULL,sizeof(int),MEM_COMMIT,PAGE_READWRITE);
    int* ptr = NULL;
    ptr = (int*)&i;
    //ptr = j; 
    /*
    malloc 是crt运行库实现 系统为crt库在进程靠近2G的高地址处保留大块堆内存 用链表管理 
    调用malloc时,单独从此处抽取分配给调用者
    */
    ptr = k;
 
    //1) 忽略malloc这另类 看看实际系统定义诺干栈变量后,查看他们在内存中的分布
    int arg1=0x40302010;
    int arg2=0x20;
    int* ptr1=NULL;
    int* ptr2=NULL;
    char str[] = {"1234567"};
    printf("arg1:%x\narg2:%x\n",&arg1,&arg2);
    printf("ptr1:%x\nptr2:%x\n",&ptr1,&ptr2);
    printf("str:%x\n",str);
    //结论:变量的地址是连续并且向下扩展
    //编译器通过sub esp,immd来开辟栈空间
    //再来看下变量地址存放的内容 arg1
    char* arg1Ptr = (char*)&arg1;
    printf("arg1Ptr[0]:%02x\narg1Ptr[1]:%02x\narg1Ptr[2]:%02x\narg1Ptr[3]:%02x\n",*arg1Ptr,*(arg1Ptr+1),*(arg1Ptr+2),*(arg1Ptr+3));
    //程序没问题吧,感觉在用arg1Ptr取arg1各个字节的内容,看下arg1在内存中的分布
    //.........................................................................
    //intel cpu小端机 (题外话 网络编程时 ip/port转换即为大小端字节转换) 数据在内存中按高高低低分布 高字节 在高位 低字节在低位
 
    //2) 知道了变量在内存中的分布 再看下如何存取变量,alt-8
    //
    arg1 = 0x80706050;
    arg2 = arg1;
    arg2 = 0x20;
 
    //程序编译后 用[ebp-N]相对偏移取变量 why? 
    //intel CPU用esp指向当前函数栈顶,变量又保存在栈中,理所当然的可以用esp取变量。
    //但是变量在不断扩充esp在不断减小 ------
    //假设 现在要取arg1变量值 可能会编译成 mov eax,[esp+0x40],意思是arg1离栈顶esp相差0x40个字节
    //如果程序又新定义一个栈变量,栈顶向下移动,即esp=esp-4 此时 esp离arg1的距离为0x44 
    //如果再次取arg1的值 会编译成 mov eax,[esp+0x44] 这样编译起来太麻烦了  
    //a) intel CPU用ebp做当前函数帧[不是强制约定 习惯],也就是栈底,某些面试宝典会写
    //函数栈底不改变,说的就是ebp再当前函数中不改变。栈变量编译后内存位置固定下来,现在ebp又是固定不变的准绳
    //这样无论程序怎样扩展栈变量,ebp到各个变量之间的距离都不会改变
    
    //3) 面试宝典还说 c++参数入栈顺序是 从右往左压栈 全部入完后 压入函数返回地址
    //既然 大家对堆栈达成共识才看到这了 再来看下调用函数,继续反汇编 观察堆栈变化
    ShowEsp(&arg1,&arg2);
    //a)入栈操作 (esp寄存器的变化) 用的是push 每次push后esp减4 压入返回地址后 jmp到ShowEsp
    //程序jmp到ShowEsp 这里也跟到esp
    
    //5) 堆栈平衡的收尾
    /*
    来看下函数返回后当前栈顶还剩啥
    指令执行顺序-|esp变化------|保存ebp后变量相对于新栈帧ebp的距离-------
    push &arg2   |1次esp=esp-4 | ebp+0x0C
    push &arg1   |2次esp=esp-4 | ebp+0x08
    */
    /*函数参数已经显得不重要了可以忽略不计,再说main函数中依然可以通过[ebp-N]的形式访问这些变量
    但是目前堆栈还没有恢复到函数调用前的样子 还倒欠main函数8个字节,于是编译器采用了一种简单粗暴
    却有行之有效的办法
    add esp,0x08
    于是堆栈平衡
    还有一个问题,函数范围值在哪?eax中
    eax 4字节 不管返回什么都没问题
    */
    exit(0);
}
 
int ShowEsp(int* arg1,int* arg2)
{
    //进入到ShowEsp后 先是取形参,然后赋值给局部变量
    //局部变量访问 已经不是这里的重点 此处重点看下如何取形参
    int op1,op2,res;
    //atl-8
    op1 = *arg1;
    op2 = *arg2;
    /*
    访问arg1 arg2 被翻译成mov eax,[ebp+8],[ebp+0x0c]
    之前访问变量是用[ebp-N]的形式 现在怎么变成使用[ebp+N]的形式?
    解释这个 还是得看反汇编的结果
    反汇编的前几句如下
    push ebp
    mov ebp,esp
    sub esp,4Ch
    上面2-a)处写到过 intel CPU用ebp做当前函数帧,刚才指令流是在main函数中,因此ebp是基于main函数的
    现在进入到ShowEsp函数中,要形成新的函数帧,因此先用push ebp把前一个函数的函数帧保存起来,然后
    mov ebp,esp把当前的栈顶esp赋值给ebp形成新的函数帧 最后的sub esp,4Ch 为本函数创建栈空间
    
    如果把之前main函数中的函数调用和参数入栈 以及此处生成新的函数帧一系列动作联合起来 并查看esp在此期间的
    变化:
    指令执行顺序-|esp变化------|保存ebp后变量相对于新栈帧ebp的距离-------
    push &arg2   |1次esp=esp-4 | ebp+0x0C
    push &arg1   |2次esp=esp-4 | ebp+0x08
    call ShowEsp |3次esp=esp-4 | ebp+0x04
    push ebp     |4次esp=esp-4 | ebp+0x00
    
    这应该能解释函数取形参用mov eax,[ebp+0x08]等形式
    */
    res = op1+op2;
 
    //4)堆栈平衡的下半段
    /*
    还是拿某宝典说事,说c++是_stdcall c是_cdcel call
    区别是函数执行结束 一个由被调用的函数恢复堆栈 另一个是由调用者恢复堆栈
    这代码是_cdcel call 由调用者恢复堆栈
    来看下这函数怎么恢复堆栈 继续返回编
    */
    return res;
    /*
    程序结尾处看到
    mov esp ebp
    pop ebp
    ret 8
    还记得函数入口处的?
    push ebp
    mov ebp esp
    都说是堆栈操作了,所有的操作要呼应对吧,执行了
    mov esp ebp
    pop ebp
    这两句之后,函数堆栈恢复到发生调用ShowEsp的情景,虽然ShowEsp分配的栈变量还存在,但
    已经处在esp指向的范围之外,换句话说,此时再新建栈变量,以前栈上的变量就会覆盖。当然
    很少有人这么做。这也是有时候函数返回了,还能得到函数内部变量的原因。注意,所谓的宝典
    会说,函数返回会自动清栈变量,反正c的代码,我没看到这种语句
    最后的ret语句会使程序返回到函数调用的地方,这个地方由谁指定?
    还记得3-a)处调用ShowEsp时,把下一条指令压入堆栈?就是那个时候指定函数返回地址,翻阅intel手册
    说发生ret时,返回到当前栈顶指向的值
    来看下当前栈顶还剩啥
    指令执行顺序-|esp变化------|保存ebp后变量相对于新栈帧ebp的距离-------
    push &arg2   |1次esp=esp-4 | ebp+0x0C
    push &arg1   |2次esp=esp-4 | ebp+0x08
    call ShowEsp |3次esp=esp-4 | ebp+0x04
    栈顶还剩call ShowEsp时压入的返回地址,ret执行后 相当于pop eax, jmp eax 弹出一个栈值。
    于是我们乘坐这个传送门返回到main函数中
    
    [题外话]如果修改这个返回地址会得到意想不到的结果,这是后面要讲的缓冲区溢出
    */
}

--------------------- 
作者:Yuri800 
来源:CSDN 
原文:https://blog.csdn.net/lixiangminghate/article/details/43195717 
版权声明:本文为博主原创文章,转载请附上博文链接!

堆栈平衡:估计这是最详细的讲解堆栈平衡的了 vc++6.0相关推荐

  1. logback配置控制打印台异常信息_logback异常输出详细信息(调用堆栈)分析

    Logback背景 Logback是一个开源的日志组件,是log4j的作者开发的用来替代log4j的. logback由三个部分组成,logback-core, logback-classic, lo ...

  2. 一文详细解析kafka重平衡机制

    前言 1.队列重平衡概述 如果对RocketMQ或者对消息中间件有所了解的话,消费端在进行消息消费时至少需要先进行队列(分区)的负载,即一个消费组内的多个消费者如何对订阅的主题中的队列进行负载均衡,当 ...

  3. 【模型修改的漫漫长路】经典VGG模型理解-这大概是目前最详细的讲解了【一】

    前言-论文好不容易写出来了,好不容易让导师看了,结果老师说模型不改只能发普通的文章,在我再三退却后变成了模型不改不给发[其实是自己太菜,论文写的太菜].用的模型什么的没理解透,现在突然说修改简直是天方 ...

  4. uboot 详细注释讲解

    转自:http://home.eeworld.com.cn/my/space-uid-135723-blogid-25548.html uboot 详细注释讲解 标签:  uboot  注释  讲解  ...

  5. 超级详细树讲解三 —— B树、B+树图解+代码

    首先很高兴你看到了这篇文章,这篇文章可能会花费你很长很长的时间去看,但是这篇文章包括的内容绝对足够你对树的一个系统性的学习.为什么要写这篇文字呢?因为自己在学习树的时候,有些博客只有图解,有些博客只有 ...

  6. 讲透学烂二叉树(五):分支平衡—AVL树与红黑树伸展树自平衡

    简叙二叉树 二叉树的最大优点的就是查找效率高,在二叉排序树中查找一个结点的平均时间复杂度是O(log₂N): 在<讲透学烂二叉树(二):树与二叉/搜索/平衡等树的概念与特征>提到 二叉排序 ...

  7. vc++ 6.0 堆栈_在C ++中使用链接列表实现堆栈

    vc++ 6.0 堆栈 To implement a stack using a linked list, basically we need to implement the push() and ...

  8. centos6.8安装oracle12C 详细步骤讲解

    2019独角兽企业重金招聘Python工程师标准>>> centos6.8安装oracle12C 详细步骤讲解 安装前环境配置 1 root身份安装依赖包 [root@dlp ~]# ...

  9. Squid代理服务器应用(服务搭建详细步骤讲解)

    Squid代理服务器应用(服务搭建详细步骤讲解) 文章目录 一.代理的工作机制 二.Squid 代理的类型 三.Squid部署 (一).安装 Squid 服务 (二).构建传统代理服务器 (三).构建 ...

最新文章

  1. mysql udf 性能_适当的mysql udf
  2. spring+mybatis整合读取不了配置文件
  3. Saber2016安装包和安装详细安装步骤
  4. QWidget中加载QML页面并设置透明背景
  5. mac解压rar命令_苹果mac电脑上很好用的免费压缩软件?ezip压缩软件分享
  6. sql server linux性能,详细了解SQL Server 2008性能和性能优化
  7. 博文写作——摘要摘要图标
  8. linux如何在C程序中使用exit,c语言exit和return区别,在fork和vfork中使用
  9. 【讨论】测试工程师能否作为一份终生职业?30岁+怎么办?
  10. [转载] 将整数k转换成实数python表达式_Python程序设计课后习题答案-第一单元
  11. [原创]补丁工具V1.6.3
  12. 西门子s7-200解密软件下载_西门子S7200plc软件仿真软件使用方法
  13. Hadoop原理与安装
  14. Java 将文件转换写入byte[]
  15. Excel进阶(2)
  16. 华为云服务器怎么更改系统版本,华为云服务器怎么更改系统版本
  17. PS渐变羽化制作单车
  18. vue 微信登录(使用了vant)
  19. opencv 绘制轮廓边框 多边形 圆形 矩形
  20. 想自学软件测试?这本《软件测试》,入门必看

热门文章

  1. 什么是详细设计说明书?
  2. CSS3transform属性详解
  3. gis是一门集计算机科学,gis是什么_GIS技术对生活生产有什么作用
  4. linux怎么看tty连接哪个端口,Linux TTY framework(4)_TTY driver
  5. c++字符串操作之std::ostringstream踩坑日记
  6. 蓝港公布3D双端动作游戏《黎明之光》 预告片首曝
  7. zephyr在Ubuntu18.04安装使用
  8. TTS(1)单片机播放WAV语音,有原理,有代码
  9. 应用程序主题研发有妙招!DevExtreme拥有多种预定义主题样式
  10. 用户停留点 计算机,基于移动轨迹中停留点的用户群体行为规律检测方法与流程...