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

想做个网站 怎么做h5和html5的区别

想做个网站 怎么做,h5和html5的区别,经典网站首页设计,网站换域名要怎么做文章目录 1. 什么是函数栈帧2. 理解函数栈帧能解决什么问题呢#xff1f;3. 函数栈帧的创建和销毁解析3.1 什么是栈#xff1f;3.2 认识相关寄存器和汇编指令常见寄存器常用汇编指令 3.3 详解函数栈帧的创建和销毁3.3.1 函数的调用堆栈#xff08;main函数也是被其它函数调用… 文章目录 1. 什么是函数栈帧2. 理解函数栈帧能解决什么问题呢3. 函数栈帧的创建和销毁解析3.1 什么是栈3.2 认识相关寄存器和汇编指令常见寄存器常用汇编指令 3.3 详解函数栈帧的创建和销毁3.3.1 函数的调用堆栈main函数也是被其它函数调用的3.3.2 main函数栈帧的创建3.3.3 小知识烫烫烫~是如何产生的3.3.4 main函数中核心代码的执行3.3.5 调用add函数传参call指令调用函数add函数栈帧的创建Add函数核心代码的执行函数结果的返回先保存到寄存器里面 3.3.6 Add函数栈帧的销毁3.3.7 返回至main函数3.3.8 main函数获取返回值3.3.9 拓展了解 1. 什么是函数栈帧 我们在写C语言代码的时候经常会把一个独立的功能抽象为函数所以C程序是以函数为基本单位的。 那函数是如何调用的函数的返回值又是如何返回的函数参数是如何传递的这些问题都和函数栈帧有关系。 函数栈帧stack frame就是函数调用过程中程序的调用栈call stack所开辟的空间这些空间是用来存放 函数参数和函数返回值临时变量包括函数的非静态的局部变量以及编译器自动生产的其他临时变量保存上下文信息包括在函数调用前后需要保持不变的寄存器。 2. 理解函数栈帧能解决什么问题呢 前期学习的时候我们可能有很多困惑 比如 局部变量是怎么创建的 为什么局部变量不初始化它的值是随机值 函数是怎么传参的传参的顺序是怎样的 形参和实参是什么关系 函数调用的具体过程是怎么样的 函数调用结束后返回值是如何返回的 那关于这些问题如果我们了解了函数栈帧的创建和销毁就会豁然开朗。 那接下来我们就来一起学习一下函数栈帧的创建和销毁的过程… 3. 函数栈帧的创建和销毁解析 3.1 什么是栈 栈stack是现代计算机程序里最为重要的概念之一几乎每一个程序都使用了栈没有栈就没有函数没有局部变量也就没有我们如今看到的所有的计算机语言。 在经典的计算机科学中 栈被定义为一种特殊的容器用户可以将数据压入栈中入栈push也可以将已经压入栈中的数据弹出出栈pop但是栈这个容器必须遵守一条规则先入栈的数据后出栈First In Last Out FIFO。 就像叠成一叠的书先叠上去的书在最下面因此要最后才能取出。 在计算机系统中 栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中也可以将数据从栈顶弹出。压栈操作使得栈增大而弹出操作使得栈减小。 在经典的操作系统中栈总是向下增长由高地址向低地址的。 在我们常见的i386或者x86-64下栈顶由名为 esp 的寄存器进行定位。 3.2 认识相关寄存器和汇编指令 常见寄存器 首先我们来了解一个东西叫做——寄存器 在计算机体系结构中寄存器是一种高速存储器用于保存指令执行过程中的数据和地址。寄存器是与处理器紧密集成的组件用于临时存储、操作和传递数据。 概念大家简单理解一下。 然后下面我们来介绍一下常用的一些寄存器 除 EBP 和 ESP 外其他几个寄存器的用途其实是比较任意的也就是什么都可以存。 那EBP 和 ESP 我们这里要给大家介绍一下 ebp 和 esp这两个寄存器在函数栈帧的创建和销毁中起着比较关键的作用。 ebp 和 esp也被称为栈基指针和栈顶指针它们两个是用来维护函数的栈帧。 那它是如何来维护的呢 大家应该知道每一个函数被调用的时候都会在内存中的栈区上开辟一块空间这块空间我们就把它称之为该函数的栈帧。 先必须明确的一点是函数栈是向下生长的。所谓向下生长是指从内存高地址向低地址的路径延伸。于是栈帧就有栈底和栈顶栈顶的地址要比栈底的低。 对 x86 体系的 CPU 而言寄存器 ebp 可称为栈基栈底指针base pointer寄存器 esp可称为栈顶指针stack pointer 而ebp 和 esp就是维护函数栈帧的ebp 叫做栈基指针存储栈底的地址 esp叫做栈顶指针存储栈顶的地址。 我们的程序中正在调用哪个函数ebp 和 esp维护的就是哪个函数的栈帧。 常用汇编指令 相关汇编命令 mov将第二个操作数寄存器的内容、内存中的内容或常数值复制到第一个操作数寄存器或内存。但不能用于直接从内存复制到内存 push数据入栈同时esp栈顶寄存器也要发生改变 pop数据弹出至指定位置同时esp栈顶寄存器也要发生改变 sub用于两个操作数相减相减的结果保存到第一个操作数中 add将两个操作数相加相加的结果保存到第一个操作数中 call函数调用1. 压入返回地址 2. 转入目标函数 jmp通过修改eipeip指令寄存器保存当前指令的下一条指令的地址转入目标函数进行调用 ret恢复返回地址压入eip类似pop eip命令 lea 指令。地址传送指令将有效地址传送到指定的的寄存器 那了解了上面的东西我们接下来就来写一个程序带大家仔细的分析一下一个完整的函数栈帧的创建和销毁的过程 #include stdio.h int Add(int x, int y) {int z 0;z x y;return z; } int main() {int a 10;int b 20;int c 0;c Add(a, b);printf(%d\n, c);return 0; }3.3 详解函数栈帧的创建和销毁 函数栈帧的创建和销毁过程在不同的编译器上实现的方法大同小异本次演示以VS2022Debug下X_86环境为例。 3.3.1 函数的调用堆栈main函数也是被其它函数调用的 那首先以上面那段代码为例我们来观察一个东西——函数调用堆栈 函数调用堆栈是反馈函数调用逻辑的 我们来调式观察一下 此时我们看到的是这样的 然后我们继续调式让它进入add函数内 那此时我们就能看到这个调用关系add函数是由main函数调用的。 函数调用堆栈是反馈函数调用逻辑的那除此之外我们可以清晰的观察到 main 函数调用之前是由invoke_main 函数来调用main函数的。 invoke_main 是一个 Microsoft C/C 运行时库中的函数用于调用程序的主函数main函数 不过在 invoke_main 函数之前的函数调用我们就暂时不考虑了 但是我们可以确定 invoke_main 函数应该会有自己的栈帧 main 函数和 Add 函数也有自己的栈帧每个函数栈帧都有自己的 ebp 和 esp 来维护栈帧空间 那接下来我们从main函数的栈帧创建开始讲解 那对于函数栈帧创建和销毁过程的研究这里我们要借助反汇编来观察和分析 调试到main函数开始执行的第一行右击鼠标转到反汇编。 注VS编译器每次调试都会为程序重新分配内存我们下面展示的反汇编代码是一次调试代码过程中数据每次调试略有差异。 另外呢为了方便下面的分析和观察我们可以做这样一件事情 右击鼠标把显示符号名取消勾选 因为我们下面重点要去观察具体的地址、内存的布局 3.3.2 main函数栈帧的创建 那我们上面分析了其实main函数也是被其它函数调用的我们上面观察到main函数是被invoke_main 调用的。 所以在main函数被调用之前invoke_main 的栈帧就应该是这样的 invoke_main 再往上我们就不管了 那下面我们就正式开始分析我们的代码 首先我们看main函数里面的第一条汇编 是push ebp前面的一串数字是该条汇编指令的地址 那push ebp做了什么事情呢 push就是压栈所以push ebp就是把ebp寄存器进行压栈放到栈顶。 那与此同时栈里面多了一个数据栈顶的位置是不是就要发生变化啊 esp要往上走那它存的地址就要减小 当然我们也可以通过监视窗口观察到它的变化 现在我们还没执行pushesp里面的值是0x008ffe20 然后我们调式汇编代码往下走 大家看变成了0x008ffe1c 那这里16进制显示的大家可以算一下差了4所以我们当前的平台下其实esp寄存器的大小就是4字节。 当然我们还可以通过内存窗口看一下它是否真的压进去了 因为现在esp指向的空间里前4个字节放到就是ebp的值 没问题。 那我们继续往下 mov ebp,esp move是把将第二个操作数的值给第一个操作数。 那这里就是把esp的值给ebp那这样ebp和esp不就指向一个位置了嘛 那大家也可以自己通过监视窗口观察一下是没问题的后面我就不带大家以以查看验证了重点是原理的理解。 这里move指令把esp的值存放到ebp中其实相当于产生了main函数的ebp这个值就是invoke_main函数栈帧的esp我们下面就可以看出来。 继续下一步 sub esp,0E4h sub指令用于两个操作数相减相减的结果保存到第一个操作数中。 所以这里就是给esp的值减去0E4H 它这里后面有个H的其实还是16进制数 大家可以算一下它对应的十进制是228 那给esp减去一个值它的值发生变化同时指向的位置也发生变化 那esp就指向一个地址更低的位置去了 那现在就是这样一个样子。 那现在我们看到ebp和esp好像又维护了一块新的空间。 那这块空间是给谁用的呢 我们现在已经开始调用main函数了所以 是的ebp和esp新维护的这块空间其实就是给main函数开辟的空间也就是main函数的栈帧 所以我们上面也提到我们的程序中正在调用哪个函数ebp 和 esp维护的就是哪个函数的栈帧 总结一下 sub会让esp中的地址减去一个16进制数字0xe4产生新的esp此时新的esp就是main函数栈帧的esp此时结合上一条指令的ebp和当前的espebp和esp之间维护了一块新的栈空间这块栈空间就是为main函数开辟的就是main函数的栈帧空间这一段空间中将存储main函数中的局部变量临时数据以及调试信息等。 那我们继续 接着再往下呢我们发现是3次push push ebx //将寄存器ebx的值压栈esp-4 push esi //将寄存器esi的值压栈esp-4 push edi //将寄存器edi的值压栈esp-4 那这3次操作呢其实我们可以不用太关心 上面3条指令保存了3个寄存器的值在栈区因为这3个寄存器的在函数随后执行中可能会被使用和修改所以先保存寄存器原来的值以便在退出函数时恢复。 再往下 这几条我们放在一块看 首先lea edi,[ebp-24h] lea呢叫做Load Effective Address即加载有效地址。 所以这句指令呢其实就是把[ebp-24h]对应的地址放到edi里面 然后mov ecx,9即把9放在ecx中 接着mov eax,0CCCCCCCCh把0xCCCCCCCC放在eax中 上面这3步之后这里真正起作用的其实就是rep stos dword ptr es:[edi]这一句 这一句是干嘛呢 它是把从[ebp-24h]这个位置开始向上的9个dword4字节直到ebp的内容全部初始化为0CCCCCCCCh 可以带大家看一下 当然它并没有覆盖的之前push进去的3个寄存器的值 这样的话虽然我们下面修改它了但是最后还可以恢复。 所以 这4句汇编我们可以认为它做的事情就是初始化main函数的栈帧空间 当然我们当前在vs2022上它这里只初始化了9*436个字节的空间不同的编译器上可能是不同的比如vs2013上它这里初始化的这一块空间就比较大这个不用太纠结。 当然如果main函数里定义的变量啥的不一样的话肯定也会有所差异。 上面的这4句代码等价于下面的伪代码 edi ebp-0x24; ecx 9; eax 0xCCCCCCCC; for(; ecx 0; --ecx,edi4) { *(int*)edi eax; }3.3.3 小知识烫烫烫~是如何产生的 那了解了上面的内容其实还能解决我们之前的一个疑惑 之前我们写的代码中如果出现了越界的情况或者打印一个没初始化的数组有时候就打印出来一堆“烫烫烫烫烫烫烫…”的东西 原因是什么呢 是因为我们没给数组初始化而main函数开辟的栈帧里面默认有些空间被初始化成了0CCCCCCCC 而它对应的汉字编码就是“烫” 所以我们打印出来是“烫烫烫烫…” 3.3.4 main函数中核心代码的执行 这些是编译器附加的。 为了让我们研究函数栈帧的过程足够清晰不要太多干扰我们可以关闭下面的选项让汇编代码中排除一些编译器附加的代码 这时再看 就没有那两句了 那走到这里我们发现其实到现在开始真正执行main函数里面的代码 那我们也来分析分析 首先mov dword ptr [ebp-8],0Ah那就是把0AH16进制对应10进制就是整数10放到地址为ebp-8位置的dword 双字4字节32位内存单元中 补充 x86 架构中 dword ptr双字4 字节 word ptr字2 字节 byte ptr字节1 字节 那这4个字节是不是就是a在内存里面存储的位置啊 我们可以观察一下 那紧接着下面两条指令 也是一样的道理分别把10和0放到对应的位置即局部变量b 、c对应的存储位置 所以 上面3句汇编代码表示的变量a、b、c的创建和初始化这就是局部的变量的创建和初始化 所以局部变量的创建时在局部变量所在函数的栈帧空间中找到对应的空间去创建的 3.3.5 调用add函数 那紧接着下面就是调用add函数了 传参 那这里调用add的话是不是首先得传参啊我们来看看 首先 mov eax,dword ptr [ebp-14h] 那它其实就是把ebp-14h位置的dword即4字节的内容放到寄存器eax里面 那ebp-14h位置存的是谁 这个位置存的不就是b的值20嘛所以这一步就是把20这个整数值 放到eax里面 然后push eax即把eax压栈 那我们继续 接着是mov ecx,dword ptr [ebp-8]把ebp-8位置4个字节的值放到ecx寄存器里面 ebp-8位置存的是谁啊 是a变量的值10 然后push ecx 所以上面这几步是在传参吗 是的其实就是在传参而且我们能发现它是从右向左传的。 所以传参其实就是把参数push到当前函数即调用者函数的栈帧空间中 但是大家可能会有点疑惑这样能传过去嘛为什么形参还是在main函数的栈帧里面呢 不急后面大家就明白了。 call指令调用函数 我们继续往下看 那传完参就是调用函数了 call 00EA10B4其实就是去调用add函数 这时我们按F11逐语句这样才会进入函数 此时跳转到了jmp这里并且我们还能发现了一个变化 esp里面的值发生了变化并且我们上面push的eax上面好像又多push进去了一个值 而且我们会发现新push进去的这个值就是前面call 00EA10B4这条指令的下一条指令的地址。 所以 call 指令首先将当前call指令的下一条指令的地址入栈然后无条件转移到由标签指示的指令 那大家思考一下这里为什么要保存一下call指令的下一条指令的地址呢 其实很容易想通。 因为我们调用完add函数之后是不是要回到main函数中继续往下执行啊。 那调用完回来之后怎么知道要从哪里继续往下执行呢 那这时候保存的这个地址是不是就起作用了找到这个地址继续往下执行就可以了。 再往下 我们再按F11jmp指令就会跳转到对应的函数里面 我们发现此时就进入到add函数里面了 add函数栈帧的创建 那我们来看一看 首先我们会发现上面这几句指令其实和当时我们分析的main函数里面的是差不多的 其实就是去给add函数开辟栈帧并初始化那当然此时ebp和esp指针就要去维护add函数的栈帧了。 那我们还是来带大家简单分析一下 首先呢又是 push ebp 把此时的ebp的值压栈 然后mov ebp,esp 之前ebp是在维护main函数的栈帧 那现在mov ebp,esp把esp的值给ebp 那此时ebp就指向这里里其实此时就变成了Add函数的栈基指针了 接着 sub esp,0CCh给esp的值减去一个0CCh 那esp存的地址就变的更低 那这些工作是在干什么 是不是就是为Add函数开辟栈帧啊此时ebp和esp维护的就是Add函数的栈帧了 再下面呢 也是和之前main函数的一样把这3个寄存器push压栈 再接着其实就是对栈帧的一些空间进行初始化初始化为0CCCCCCCCh 即从ebp-0Ch0Ch对应10进制12开始往上的3个dword的空间初始化为0CCCCCCCCh Add函数核心代码的执行 那再往下 就是Add函数里面具体代码的执行了 首先mov dword ptr [ebp-8],0 把0 放到ebp-8的位置 其实就是创建Z 那再往下终于要执行XY的计算了 首先mov eax,dword ptr [ebp8]把ebp8位置的值放到eax里面 那这个位置放的是谁的值啊 ebp8这个位置不就是我们刚才传参的时候把实参a的值传过来放到这里了嘛那我们现在是不是就获取到传过来的参数了 再往下看 add eax,dword ptr [ebp0Ch]即 把ebp0Ch10进制是12位置的值和eax里面的值相加结果放到eax里面 那eax里存的是传过来的实参a的值10那ebp0Ch位置放到又是谁呢 哦豁 不就是传过来的实参b的值20嘛。这就是形参访问 那现在1020结果30就放到了eax里面。 所以 这里的分析很好的说明了函数的传参过程以及函数在进行值传递调用的时候形参其实是实参的一份拷贝。对形参的修改不会影响实参。 那我们调用Add函数不就是要算ab对应形参是x、y的和嘛 那我们现在就得到结果了 那然后呢 mov dword ptr [ebp-8],eax即把eax里面存的值放到ebp-8的位置。 这个位置是谁啊 不就是Add要返回的Z嘛 30 放到Z的内存单元里。 函数结果的返回先保存到寄存器里面 那现在Add函数得出结果了然后是不是要把结果返回调用函数的地方啊 那就是return Z 那我们来分析一些函数返回值是如何返回的 首先 mov eax,dword ptr [ebp-8]ebp-8位置是谁啊 是最终的结果z。 所以它又把Z的值放到了eax寄存器里面。 那大家思考一下为什么不直接返回z呢为什么要要把结果放到寄存器eax里面呢 因为 z是一个创建在Add函数栈帧上的局部变量啊Add函数调用结束栈帧销毁我们还找得到z吗 找不到了。 所以先把z的值给一个寄存器保存起来因为寄存器是存在于CPU内部的一组用于存储和处理数据的高速存储器它是不会随着函数调用结束而销毁的。 这样即使函数调用结束回到main函数里面我们也照样可以安全的拿到返回值。 3.3.6 Add函数栈帧的销毁 那我们继续往下看 紧接着下面3个pop 把之前压到栈帧里面的3个寄存器poppop edi #弹出栈顶元素送到 edi出去恢复这3个寄存器之前存的值同时esp指针变动pop一次加一个4 再往下 mov esp,ebp把ebp的值给esp 那esp就和ebp指向一个位置了 那与之对应的就是Add函数的栈帧被销毁了 现在就变成这样了 然后再往下 pop ebp弹出栈顶的值给ebp此时栈顶放到是什么 是之前压上去的main函数对应的栈基指针的值 那现在把它pop掉并把它的值给ebp。 那就变成这样了 我们发现此时ebp和esp又重新维护起了main函数的栈帧这当然没问题因为此时Add函数已经调用结束就要回到main函数了。 所以前面我们为什么要main函数的ebp栈基指针push存起来其实就是为了函数调用结束回来的时候我们能获取到原来main函数对应的栈基指针的值从而使ebp重新指向main函数的栈底维护main函数的栈帧。 3.3.7 返回至main函数 那我们再继续 我们看到下一句是retreturn那ret其实就要真正实现Add函数的返回回到调用它的main函数了。 那ret如何知道要返回到哪里呢 我们来看 此时栈顶放的是什么 是之前call指令压入栈顶的call指令的下一条指令的地址。 而我们的Add返回到main函数之后要从哪里继续往下执行 就是从call指令的下一条指令开始往下执行啊因为当初调用函数就是从call指令跳转过去的啊。 所以 ret指令实现子程序的返回机制ret 指令pop弹出栈顶保存的call执行的下一条指令的地址然后无条件转移到保存的指令地址处 所以ret之后main函数的栈帧就是这样的 那此时大家也应该非常清楚当初为什么要保存call指令的下一条指令的地址了。 那我们再继续往下执行的时候 现在在ret 我按F10 直接就回到call指令的下一条指令了 3.3.8 main函数获取返回值 那我们继续看剩下的一些指令 接下来是add esp,8 给esp的值8这是干什么啊 我们看到此时Add函数已经调用结束形参x、y还有用吗 当然没有了所以让esp8 就把形参x、y的空间也释放掉了 那我们再往下看 mov dword ptr [ebp-20h],eax把eax存的Add函数的返回值的值复制到ebp-20h位置的4个字节的内存单元上 ebp-20h10进制32位置放的是谁啊 就是c啊。 而我们main函数里面接收Add的返回值不就是用c接收的嘛 那此时main函数终于成功获取到了Add函数的返回值。 可以看出来本次函数的返回值是由eax寄存器带回来的。程序是在函数调用返回之后在eax中去读取返回值的。 那再往下是printf函数的调用接着就是main函数栈帧的销毁那它们和Add函数的调用以及栈帧的销毁是差不多的我就不过多赘述了。而且我们的文章到这里篇幅已经很长了我自认为本篇文章的讲解还是非常详细的相信大家很容易可以看懂。 3.3.9 拓展了解 其实返回对象时内置类型时一般都是通过寄存器来带回返回值的返回对象如果时较大的对象时一般会在主调函数的栈帧中开辟一块空间然后把这块空间的地址隐式传递给被调函数在被调函数中通过地址找到主调函数中预留的空间将返回值直接保存到主调函数的。具体可以参考《程序员的自我修养》一书的第10章。 到这里我们给大家完整的演示了main函数栈帧的创建Add函数栈帧的创建和销毁的过程相信大家已经能够基本理解函数的调用过程函数传参的方式。 相信现在大家再去看我们刚开始提出的哪些问题就豁然开朗了…
http://www.yutouwan.com/news/80009/

相关文章:

  • 莆田网站建设建站系统网页界面图
  • 一级做c爱片的网站新东方考研培训机构官网
  • 天津河东区网站建设wordpress同步发帖
  • 宁夏城乡和住房建设厅网站wordpress 如wp_query
  • 莱芜做网站优化网站推广怎么样
  • 秦皇岛做网站外包电子商务主要是什么
  • 本地集团网站建设做网站的公司那家好。
  • 如何用自己公司网站做邮箱国投集团网站开发
  • 怎样做号网站优化哪个网站可以免费学编程
  • 漳州建设网站wordpress pdf预览
  • 网站建设需求说明书怎么写银川360推广 网站建设
  • asp.net做网站吗虚拟主机购买网站
  • 学网站建设 去那里文登区住房和城乡建设局网站
  • dw可以做有后台的网站么用ps给旅游网站做前端网页
  • 我想做网站怎么做企业官网定制
  • 网站建设下载模板之后怎么修改wordpress分类页面不显示内容
  • 东莞网站关键字郑州铭功路网站建设
  • 网站的手机客户端怎样做python手机版
  • 途牛旅游网站建设目的网站建设使用的基本技术
  • 请问聊城做网站网站建设后应该干什么
  • 邢台做企业网站wordpress导航链接地址都是主页
  • 为什么访问外国网站速度慢qq空间做宣传网站
  • 做网站协调国内搜索引擎排名第一的是
  • 品牌网站建设预算网站加入搜索引擎怎么做
  • 灵台县住房和城乡建设局网站wordpress seo模块
  • 网站工信部不备案吗c4d培训
  • 网站设计的公司概况简介discuz网站模板
  • 如何建立一个网站英语作文机关门户网站建设意义
  • 电子商务网站设计的书甜品售卖网站网页设计
  • 营业执照上有以上除网站制作网站设计与制作前景