(声明:以下均为个人理解陈述,如有错误,还请指出)
函数栈帧

前言
在学习完函数与数组部分知识后,我们常会遇见因数组越界导致死循环和函数无限递归导致栈溢出等问题。这里我们就从汇编代码入手,去理解我们所编制的代码是如何运行的,从而顺解问题。
内存地址
首先,我们得了解一个概念,信息是需要储存的。计算机是无法直接与我们对话获取信息的,因此,科学家们构造了计算机语言。我们所编制的代码通过层层转化为二进制语言,供计算机读取。
那信息要如何储存呢?计算机只能读懂0和1,即开与关。
首先,计算机的CPU与内存之间是通过地址总线连接。每一根线都可以表示0或1,即每根线都能代表两种含义。对于32位的计算机,总数就有232种(4G)。
那是不是每一根线都是一个地址呢?显然不是,通过查阅ASCII表,我们可知一共有128个字符,而每一个字符都是最基本的信息组成元素。如果我们要表示全,那必须需要8根线组成一个单元(八个二进制位)。于是,科学家就规定,一根线为一个bit,八个bit组成一个byte。最终,我们可以确定一个byte为一个地址单元,进行储存信息。
从而进一步演化为我们所见的数据类型(char、short、int等)。
汇编指令
这里涉及的汇编指令仅含下文我所讲的例子中涉及的汇编指令(完整解释各字符串含义在下文)。
xor
xor:两个数进行逻辑异或运算,二进制位相同0,不同为1,结果赋值给第一个数。
mov
mov:两个数进行赋值操作,将第二个数的值赋给第一个数。
lea
lea:用于计算有效地址并将其加载到指定的寄存器中。这不是进行实际的内存引用,而是用于地址计算。
rep stos
rep:重复其上面的指令。ecx的值是重复的次数。
stos:将eax中的值拷贝到rdi指向的地址.
push与pop
push:指令用于将一个寄存器或值压入栈中。栈是一种后进先出(Last In First Out)的数据结构,常用于保存函数参数、局部变量或者临时数据。
pop:与push相反,将堆栈段中的一个字节弹出到某个寄存器或段寄存器或内存单元。
add与sub
add:将两个数相加,并将结果存储在第一个操作数中。
sub:将两个数相减,并将结果储存在第一个操作数中。
call与ret
call:调用一个过程(函数),它将下一条指令的地址(也就是返回地址)压入栈中,并跳转到指定的过程地址去执行。
ret:用于从一个过程返回。当执行RET指令时,它会从栈中弹出返回地址,并跳转到该地址继续执行。
test与 jl与jmp
test:将两个操作数进行逻辑与运算,并根据运算结果设置相关的标志位。
jl:若(有符号)结果小于判定值,跳转到目标地址执行指令。
jmp:是无条件跳转指令,它告诉处理器无条件地将控制权转移给指定的地址。无论什么情况,jmp 指令后的指令都会被处理器忽略,并跳转到目标地址执行指令。
例题讲解(逐句讲解)
下面以VS2022环境讲解一个简单的加法函数(不同编译器略有差异,但原理相同)
源码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 0;
int b = 0