当前位置: 首页 > news >正文

高校支付网站建设费需要入无形资产百度客服电话

高校支付网站建设费需要入无形资产,百度客服电话,wordpress 验证码插件,国内翻国外加速器写在前面 这个模块临近C语言的边界,学起来需要一定的时间,不过当我们知道这些知识后,在C语言函数这块我们看到的不仅仅是表象了,可以真正了解函数是怎么调用的。不过我的能力有限,下面的的知识若是不当,还…

写在前面

这个模块临近C语言的边界,学起来需要一定的时间,不过当我们知道这些知识后,在C语言函数这块我们看到的不仅仅是表象了,可以真正了解函数是怎么调用的。不过我的能力有限,下面的的知识若是不当,还请各位斧正。

知识点储备

  • 初步了解函数( 这里的所说的函数我们默认为自定义函数)
  • 了解C程序地址空间
  • 基本的寄存器
  • 知道一些汇编语言

函数的概念

函数大家应该都很熟悉了,这里就不细说了。我们看看就行

ret_type fun_name(para1, * )
{statement;  //语句项
}ret_type  返回类型
fun_name  函数名
para1     函数参数  

虚拟地址空间

我们一直说 :“全局变量的生命周期是所在的整个程序”、“static修饰的变量的生命周期变长了”、以及“最重要的临时变量出函数就要被销毁”。不过我们要知道这是因为什么.在C语言中我们所创建的每一个变量都会有自己空间的的存储类别,就比如汽车一般不会停在高楼那样,每一个事物都会有自己的集合,计算机的数据存储也是如此,我们这看一下.

在计算机中,我们把内存分为若干的区间(这里暂时这样理解),每一个区间保存特定的数据,我们先来看C语言程序地址空间,也是虚拟地址空间.

image-20230301103430508

上面不做解析,我们在后面会学到,大家把这个图给记住就可以了.看一下代码,来验证一下.可以看出局部变量存储在栈上且栈空间是沿着向低地址方向开辟的,堆区与之相反.

#include<stdio.h>                                                                             
#include<stdlib.h>int g_val1 = 10;
int g_val2 = 10;
int g_val3;
int g_val4;int main()
{const char* str = "abcdef";printf("code: %p\n", main);printf("read only : %p\n", str);printf("init g_val1 : %p\n", &g_val1);printf("init g_val2 : %p\n", &g_val2);printf("uninit g_val2 : %p\n", &g_val3);printf("uninit g_val2 : %p\n", &g_val4);char* p1 = (char*)malloc(sizeof(char*) * 10);char* p2 = (char*)malloc(sizeof(char*) * 10);printf("heap addr : %p\n", p1);printf("heap addr : %p\n", p2);printf("stack addr : %p\n", &str);printf("stack addr : %p\n", &p1);printf("stack addr : %p\n", &p2);return 0;
}

image-20230301103628715

寄存器

函数的调用与CPU中的寄存器有很大关系,下面有一些基本知识

  • eax:通用寄存器,保留临时数据,常用于返回值
  • ebx:通用寄存器,保留临时数据
  • ebp:栈底寄存器
  • esp:栈顶寄存器
  • eip:指令寄存器,保存当前指令的下一条指令的地址,衡量走到了那一步

简单汇编语言

这里是一些常见的汇编语言的指令,这里先和大家列出,后面我们产看汇编语言的时候,直接回来看这些语言是什么意思.

  • mov:数据转移指令
  • push:数据入栈,同时esp栈顶寄存器也要发生改变
  • pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变
  • sub:减法命令
  • add:加法命令
  • call:函数调用,1. 压入返回地址 2. 转入目标函数
  • jump:通过修改eip,转入目标函数,进行调用
  • ret:恢复返回地址,压入eip,类似pop eip命令

函数栈帧

看了这么多知识,我们一定会感到很是枯燥,觉得这和函数栈帧一点关系都没有,不要着急,下面就开始我们正式的内容。这里为了便于理解,我们这么看栈的空间,我们就多画些图片

我们知道 main函数也是一个函数,它也是能够被调用,所以main函数也会形成栈帧。

image-20230301105834840

样例代码

下面我们用一个很简单的代码来和大家简单的谈函数的原理,代码虽小,但是却能反应问题.

int MyAdd(int a, int b)
{int c = a + b;return c;
}int main()
{int x = 0xA;int y = 0xB;int z = 0;z = MyAdd(x, y);printf("z = %d\n", z);return 0;
}

我们直接开始调试,然后转到反汇编,打开寄存器.
在这里插入图片描述

汇编代码

我将汇编代码复制下来,我们一步一步分析这些东西.

int main()
{
00821E40  push        ebp  
00821E41  mov         ebp,esp  
00821E43  sub         esp,0E4h  
00821E49  push        ebx  
00821E4A  push        esi  
00821E4B  push        edi  
00821E4C  lea         edi,[ebp-24h]  
00821E4F  mov         ecx,9  
00821E54  mov         eax,0CCCCCCCCh  
00821E59  rep stos    dword ptr es:[edi]  
00821E5B  mov         ecx,82C003h  
00821E60  call        0082130C  int x = 0xA;
00821E65  mov         dword ptr [ebp-8],0Ah  int y = 0xB;
00821E6C  mov         dword ptr [ebp-14h],0Bh  int z = 0;
00821E73  mov         dword ptr [ebp-20h],0  z = MyAdd(x, y);
00821E7A  mov         eax,dword ptr [ebp-14h]  
00821E7D  push        eax  
00821E7E  mov         ecx,dword ptr [ebp-8]  
00821E81  push        ecx  
00821E82  call        008211E5  
00821E87  add         esp,8  
00821E8A  mov         dword ptr [ebp-20h],eax  printf("z = %d\n", z);
00821E8D  mov         eax,dword ptr [ebp-20h]  
00821E90  push        eax  
00821E91  push        827BCCh  
00821E96  call        008213A2  
00821E9B  add         esp,8  return 0;
00821E9E  xor         eax,eax  
}
00821EA0  pop         edi  
00821EA1  pop         esi  
00821EA2  pop         ebx  
00821EA3  add         esp,0E4h  
00821EA9  cmp         ebp,esp  
00821EAB  call        00821235  
00821EB0  mov         esp,ebp  
00821EB2  pop         ebp  
00821EB3  ret  

这里把寄存器再次简单的说一下.

  • ebp指向栈底
  • esp指向栈顶
  • eip指向下一个即将执行的地址 还未执行

main函数栈帧形成

注意,main函数也是有栈帧的,这里我们默认mian函数栈帧已经创建完成了,只看MyAdd函数栈帧的形成,他们都是一样的,我们希望以最小的成本来和大家经行分析.

代码逻辑

下面开始main函数内部大代码逻辑,这是局部变量空间的开辟.

int x = 0xA;
01011E65  mov         dword ptr [ebp-8],0Ah //在ebp-8处在开辟一个空间,将x的值放进去

image-20230301105806031

	int y = 0xB;01011E6C  mov      dword ptr [ebp-14h],0Bh  //在ebp-14处在开辟一个空间,将y的值放进去

image-20230301110453899

		int z = 0;00821E73  mov   dword ptr [ebp-20h],0  //在ebp-20处在开辟一个空间,将z的值放进去 

image-20230301110638966

我们可以通过汇编语言看出可以看出,x、y、z 的空间是不连续的 ,这是VS保护机制, 防止一些程序员猜测对应的地址,不过一些老的编译器是不会这么做的,这里不要求我们记忆.

形参拷贝

下面汇编语言继续往下面走,我们此时遇到了函数的调用,那么编译器首先要做的就是形参的拷贝.

这就话的意思是把ebp-14(也就是y) 赋值给eax eax是一个临时的寄存器,保留临时数据,常用于返回值,这里的寄存器都是一个临时的容器,方便

	z = MyAdd(x, y);
00821E7A  mov         eax,dword ptr [ebp-14h]  
在这里插入图片描述

此时编译器会做下面动作.把eax里面的数据推送到栈中.push命令将eax的值放入栈中,同时栈顶esp的位置发生变化,变化的大小是4个字节,因为y是int型

00821E7D  push        eax  

image-20230301111820019

此时我们更新一下我们地址空间的总图,把栈顶寄存器的值保存一下.

image-20230301112017071

下面同理,我们也罢x的值进行拷贝一番,把ebp-8(也就是x) 赋值给ecx,然后推送到栈帧中.

    00821E7E  mov         ecx,dword ptr [ebp-8]  00821E81  push        ecx  
在这里插入图片描述

我们还是要更新一下栈顶的位置发生变化.
image-20230301112249321

下面我们开始总结一下上面的过程.

  • 编译器首先为main函数开辟栈帧,然后按照顺序进行变量空间的开辟和初始化
  • 遇到调用函数时,临时变量的形成(实参的临时拷贝)在函数调前就完成了
  • 形参实例化的顺序是从右向左依次形成的,这个有很大的用处,只不过在C++中会体现
  • 形参的空间是紧邻的,毕竟形参的变量空间的创建伴随着栈顶的变化.

函数调用

下面我们正式开始函数的调用工作,此时涉及很多的东西.这里先说一下call命令的作用

  • 压入返回地址 (最重要的)
  • 转入目标函数

我们说压入返回地址,此时谁是返回地址呢call命令的下一条命令的地址. 为什么要压入?根本原因是函数调用完毕,我们要放回mian函数执行其他的代码,需要返回就需要一个回来的坐标.

00821E82  call        008211E5

在这里插入图片描述

此时我们一跳这个指令,那么此时编译器会做两个事情,把call指令的地址压入栈顶,顺便修改esp,让后进入call 的地址处.

在这里插入图片描述

下面开始jump指令,这是一个跳转指令,把寄存器eip的数据从当前指令的地址改变成jump的地址,我们把这个目的地址认为是我们调用函数的地址,此时转入目标函数.

image-20230301115914240

这里存在一个问题, esp 也就是栈顶的指针,为何发生变化了?这是因为call指令的第一个作用,至于改变了多少,我们先不关心,只需要知道上升的空间里面是保存一个指针的,那么就是4个或者8个字节.

image-20230301192710989

MyAdd函数栈帧的形成

我们继续开始下面最关键的一个步骤,由于我的不小心,把代码从调试状态退出来了,此时这重新进入调试,那么汇编语言中的相关地址会发生变化,不过代码的逻辑是不会发生变化的,我们还是把汇编语言进行复制出来,这里只看MyAdd函数的汇编语言.

int MyAdd(int a, int b)
{
001E2EC0  push        ebp  
001E2EC1  mov         ebp,esp  
001E2EC3  sub         esp,0CCh  
001E2EC9  push        ebx  
001E2ECA  push        esi  
001E2ECB  push        edi  
001E2ECC  lea         edi,[ebp-0Ch]  
001E2ECF  mov         ecx,3  
001E2ED4  mov         eax,0CCCCCCCCh  
001E2ED9  rep stos    dword ptr es:[edi]  
001E2EDB  mov         ecx,1EC003h  
001E2EE0  call        001E130C  int c = 0;
001E2EE5  mov         dword ptr [ebp-8],0  c =a + b;
001E2EEC  mov         eax,dword ptr [ebp+8]  
001E2EEF  add         eax,dword ptr [ebp+0Ch]  
001E2EF2  mov         dword ptr [ebp-8],eax  return c;
001E2EF5  mov         eax,dword ptr [ebp-8]  
}
001E2EF8  pop         edi  
001E2EF9  pop         esi  
001E2EFA  pop         ebx  
001E2EFB  add         esp,0CCh  
001E2F01  cmp         ebp,esp  
001E2F03  call        001E1235  
001E2F08  mov         esp,ebp  
001E2F0A  pop         ebp  
001E2F0B  ret 

压入栈底

此时编译器首先要做的就是把原本栈底寄存器ebp中的数据压入到栈中,同时栈顶也发生变化

00821740  push        ebp  

这条命令是将ebp(也就是栈底)的内容压入栈中,同时栈顶也发生变化
image-20230301193616424

修改栈底

下面开始数据转移指令,该命令的意思是将esp的内容覆盖到ebp中,也就是此时栈底变得和栈顶一样了,该过程没有通过内存,直接通过CPU. 那么我们可能会发出疑惑,那main函数栈底怎么办,是不是找不回来了?实际上不是的,上一步我们不是把栈底的内容给保存了吗!

00821741  mov         ebp,esp 

image-20230301194044842

修改栈顶

下面我们开始计算新的栈顶就是在哪里了,根据sub指令,可以计算出新的栈顶在哪里.该命令的意思是esp减去一定的值,结果放在esp中到这里我们已经简单的形成了MyAdd函数的栈帧了

00821743  sub         esp,0CCh//0CCh 的大小和你定义的函数的规模有关

image-20230301194506718

代码逻辑

下面我们开始函数内部的代码逻辑,都是很简单的.这和main的变量开辟一样

int c = 0;
001E2EE5  mov         dword ptr [ebp-8],0  //在ebp-8处在开辟一个空间,将c的值放进去

image-20230301194817461

下面我们开始一步步分析后面的逻辑,注意看,我们上面形参的拷贝都是把数据放在了寄存器中,此时要是想要使用我们形式参数的数据,那么需要编译器手动的取出.

c =a + b;
001E2EEC  mov         eax,dword ptr [ebp+8]  
001E2EEF  add         eax,dword ptr [ebp+0Ch]  
001E2EF2  mov         dword ptr [ebp-8],eax 

先例解释这一条,把ebp+8放在eax中.那么ebp+8是多少呢?答案就是我们的x值的拷贝

001E2EEC  mov         eax,dword ptr [ebp+8]  

image-20230301195651443

同理,这个命令是将ebp+0Ch的内容和eax加起来放到eax中.ebp+0Ch就是y值的拷贝

001E2EEF  add         eax,dword ptr [ebp+0Ch] 

image-20230301195843234

这里是将eax写入到ebp-8,也就是c中,就是加法计算的结果.

001E2EF2  mov         dword ptr [ebp-8],eax 

image-20230301200133091

返回值

其中我们函数栈帧是如何得到的已经了解了,我们来看函数栈帧的最后的一部分.

保存返回值

我们把计算出的结果保存到寄存器中,为了后面的使用

001E2EF5  mov         eax,dword ptr [ebp-8]  

image-20230301200656698

释放栈帧

此时我们函数已经结束了,现在是函数的后期处理工作,包含资源的清理.把ebp 覆盖到esp,也就是收缩栈顶到栈底,这一步可以称为“释放栈帧”,注意函数栈帧的释放可不是空间的释放,只不过是它变成无效,也就是释放栈帧是不等于释放空间的.

001E2F08  mov         esp,ebp  

image-20230301200918352

弹栈

此时发生了弹栈操作,将main函数的栈底放在ebp中,esp内容发生改变.记住了我们在开辟函数栈帧前已经把main函数栈底的地址压入栈中的,此时拿到就可以了.

001E2F0A  pop         ebp  // pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变

image-20230301201210296

注意,此时我们还是处于MyAdd汇编语言的执行部分,此时我们应该返回原本mian函数调用函数的下一个地址,此时我们需要拿到我们曾经的保存在栈中的地址,压入到eip,要知道eip是编译器下一条要到的地址,esp内容改变

001E2F0B  ret  //类似pop eip命令

image-20230301202702935

释放空间

注意此时我们已经出了MyAdd函数的汇编语言了,已经开始了函数调用的后续工作,例如此时要释放临时拷贝的变量的空间,意思是esp+8放在esp中,也就是修改栈顶.

	z = MyAdd(x, y);
00AC1440  add         esp,8  
00AC1443  mov         dword ptr [z],eax  

image-20230301201756600

我们开始继续执行函数调用后的代码,将eax的值放到ebp-20,也就是z,后面mian函数的栈帧的销毁我也就不分析了,逻辑是一样的.

001E1E8A  mov         dword ptr [ebp-20h],eax 

总结

我来一个简单测总结,这个博客只要求大家理解就可以了.

  • 函数的的栈帧是由编译器决定的,C语言中很多数据类型,那么编译器有能力知道所有类型变量的大小。

  • push进去的变量的空间是连续的,所谓的push进入额变量就是 形参拷贝,压入返回值,压入main栈底的操作是连续的

  • 0CCh 的大小和你定义的函数的规模有关,编译器会自动计算.

我们看到了push变量空间是连续的,也就是我们可以通过形参的地址来修改另外的地址.

void MyAdd(int a, int b)
{printf("before: b = %d\n", b);*(&a + 1) = 100;printf("after : b = %d\n", b);
}int main()
{int x = 0;int y = 0;MyAdd(x,y);return 0;
}

image-20230301222008329

那么此时也会做一些更加大胆的事情,例如修改mian函数的栈底和修改放回值,这就会让我们代码不能继续mian函数后续代码了,不过现在在VS是非常做测试了,它做的安全性更加强了.

如果面试官问你你可以讲解一下函数栈帧的底层是什么,可以简单的说一下流程吗?这里我给出下面自己的一个答案,注意回答问题的时候一定要说是自己根据观察VS系列编译器理解的,C语言中的函数栈帧创建流程通常包括以下几个步骤:

  1. 函数参数被压入栈中。在调用函数之前,函数的参数被压入栈中,按照从右到左的顺序进行压栈,
  2. 函数被调用时,系统会将函数的返回地址压入栈中,同时将栈顶指针(Stack Pointer,SP)减少相应的大小,顺便把mian函数的栈底压入栈中
  3. 函数局部变量被压入栈中。函数局部变量也是在栈中分配的,同样按照从右到左的顺序进行压栈。当函数返回时,这些局部变量所占用的栈空间也将被释放。
  4. 在执行函数体之前,为函数创建栈空间。调整栈指针的大小通常是在编译时计算出来的。
  5. 进行函数体的逻辑运算,包含变量的空间创建,使用形参的过程,返回值会被保存在一个寄存器中
  6. 释放栈帧,弹栈,返回地址被弹出栈中,释放形参的空间,调整栈顶,继续执行函数调用后面的代码.
http://www.wooajung.com/news/30761.html

相关文章:

  • 网站建设有利点全网推广
  • 网站的手机客户端怎样做怎么查百度收录
  • 政务服务中心 网站建设百度推广需要什么条件
  • 集团公司网站改版方案舆情监测软件免费版
  • 自己做的网站上出现乱码怎么修改湖南广告优化
  • 同ip网站做排名seo百度推广如何代理加盟
  • 网站外链是什么意思辅导机构
  • 建设部网站查询百度搜索引擎的使用方法
  • 做网站设计的总结友情链接你会回来感谢我
  • 网站建站推广新媒体平台
  • 专业建设报告零基础seo入门教学
  • 做一钓鱼网站吗百度关键词优化排名
  • 商城微信网站开发王通seo
  • 二手房交易网站开发源码网络市场的四大特点
  • 重庆建筑模板生产厂家徐州关键词优化排名
  • 水源logo设计制作网南宁市优化网站公司
  • 功能性网站制作专门发广告的app
  • 网站重构div css论文百度推广登录后台
  • 国外的有趣设计网站网站的优化从哪里进行
  • wordpress调整行间距seo排名优化方法
  • 东莞市网络营销推广怎么样搜索引擎的优化和推广
  • 西安做兼职网站百度推广销售话术
  • dw个人网站模板搜索引擎营销优化策略有哪些
  • 查网站是不是用shopify做的站长网站大全
  • 国外平面设计分享网站有哪些站长工具seo综合查询广告
  • 枣庄市住房和城乡建设局网站网站设计公司排行榜
  • 做网站项目的弊端google chrome download
  • wordpress 做网站网络营销10大平台
  • 银川网站建设哪家好百度竞价sem入门教程
  • 网站怎么做跟踪链接福州seo推广优化