【汇编 C】函数嵌套底层原理堆栈图、为什么局部变量一定要赋初始值

17 篇文章 1 订阅

目录

前言

代码准备

绘制堆栈图

局部变量为什么一定要赋初始值?

总结

结语


前言

        对于堆栈图不够了解的话,可以先观看这篇文章《函数调用底层堆栈图》。此外,对于编译器最好使用VC++6。因为我当前使用的是win11操作系统,所以只能使用vs2010进行讲解。对于vs2010和VC++6对于局部变量在内存中的处理是不一样的,本篇文章以VC++6为标准。

代码准备

        测试代码test01.cpp:

int pluso(int x, int y)            // plus内嵌函数
{
    return x+y;
}

int plus(int x, int y, int z)      // plus调用pluso函数
{
    int m = pluso(x,y);
    return m+z;
}

int main()                         // 主函数,程序入口
{
    __asm mov eax,eax;             // 无用的汇编代码,用来设置断点
    plus(1,2,3);                   // 调用plus,plus再调用pluso
    return 0;
}

        在__asm关键字处加上断点,然后运行,再使用ALT+8转到反汇编窗口即可(这里提示一下,每次编译还是尽量使用ctrl+alt+7因为这样是重新生成的。如果不重新生成,有的时候汇编代码会很乱)。

        还是和以前一样,一行指令对应一个堆栈图。

        堆栈图也是和以前一样,使用excel表。

绘制堆栈图

        堆栈图初始状态:

        先看plus调用函数的实现:

 

        push 3: 

        push 2: 

        push 1: 

        这里最好做一下记号,之后平栈的时候好比较:

 

        call plus (0AF110Eh): 

        接下来看函数内部实现:

 

        push ebp:

 

         mov ebp,esp:

        sub esp,0CCh: 

        push ebx:

 

 

        push esi:

 

 

         push edi:

 

        lea edi,[ebp-0CCh]: 

        该步骤是将提升堆栈后的那个地址存放在edi寄存器中,堆栈无变化

        mov ecx,33h:

        ecx中存放十六进制的33,堆栈无变化

        mov eax,0CCCCCCCCh:

        eax中存满断点0xCC,堆栈无变化

        rep stos dword ptr es:[edi]:

        该步骤是:重复ecx次将eax中的断点赋值给从edi中的值开始的地址。

        上面几步执行后堆栈的变化:

 

        接下来看函数中嵌套调用函数的底层原理: 

        mov eax,dword ptr [y]: 这里如果使用vc6的话,y会显示具体值。

        这条指令是将y地址中的值取出来放在eax寄存器中。堆栈无变化。

        我们知道x=1,y=2,z=3。在我们上面画的堆栈图里找到2。他的位置应该是ebp+C,因为从右往左传参,所以ebp+C如果是2,那么ebp+8应该是1,ebp+10就是3。自己可以用寄存器窗口中的ebp加上8或者C或者10,然后再去内存窗口中查看结果是否对的上。(注意这里的10是十六进制的)。

        0075F8B0+8 = 0075F8B8:

         我们继续。

         push eax:

        ecx,dword ptr [x]:

        push ecx:

        上面这两步就是把x(ebp+8)取出来然后push到堆栈中。

        通过第一次调用plus函数的时候,我们发现函数的参数就是靠push传参的。所以这里两次push就是把x,y传给函数pluso。 

        堆栈变化:

         call pluso (0AF10A0h):调用函数

        pluso函数的代码:

 

        push ebp: 

        mov ebp,esp:

 

         sub esp,0C0h:

        push ebx;push esi;push edi: 

        lea edi,[ebp-0c0h];

        mov ecx,30h;

        mov eax,0CCCCCCCCh;

        rep stos dword ptr es:[edi]: 

        mov eax,dword ptr [x]:(这是vs2010显示的汇编) 

        如果是VC6,应该是这样的:

        mov eax,dword ptr [ebp+8]:(拿出第一个参数)

        当前ebp:

        ebp+8 =  0075f7d0

        查看0075f7d0:

        所以这一步把"1"取出来了放在了eax里。 

        add eax,dword ptr [y]:

        这一步是把y的值取出来与eax里的值相加再放入eax中。

        pop edi;

        pop esi;

        pop ebx: 

        mov esp,ebp: 

        pop ebp: 

        ret: 

        平栈区操作:

 

        add esp,8:外平栈 

        mov dword ptr [m],eax:

        将x+y存放在eax中的值,赋值给m这个地址。m就是plus函数中定义的局部变量,局部变量一定是在缓存区中的,但是在哪里就不知道了,因为每个处理器的处理方式不一样。 

        上面这两行是将m中的值再取出来放到eax中,然后将上z的值,最终得到的结果存放在eax中,因为eax寄存器一般都用来存放返回值。

        pop edi;

        pop esi;

        pop ebx;        //这是将plus函数之前备份的寄存器出栈

         add esp,0cch:

        cmp ebp,esp;

        call @ILT+305()(0AF1136h)

        这两条指令是一起的。cmp指令会判断esp与ebp的值。

        如果值相等,调用0AF1136h这个地址的函数就不会出错,程序继续执行

        如果值不相等,函数就会报错。 

        mov esp,ebp:没什么用

        pop ebp:

        ret: 

         

        add esp,0ch: 

        xor eax,eax:将eax置零: 

        下面的指令就是主函数的出栈了,主函数也只不过是一共普通的函数,所以出栈也没什么太大的区别。 

局部变量为什么一定要赋初始值?

        使用vs的朋友可能不容易发现,因为变量的地址,在vs中都被变量名代替了。但是使用VC6的朋友可以发现:函数中的局部变量都是定义在缓冲区中的

        这就导致一个什么样的问题呢?

        仔细看看我们绘制的堆栈图,最后函数返回的时候,我们一直在将esp和ebp返回到使用函数前的地址。但是!!!我们并没有清空缓冲区啊。也就是说我们函数调用的时候,提升了堆栈、使用了堆栈,但是函数返回的时候并没有清空堆栈啊。如果再次调用其他的函数,我们再一次提升堆栈的时候,这个时候定义的局部变量,他的地址有可能会使用到以前使用过的地址,这样的话如果局部变量不赋初始值,很有可能导致使用的值是一个垃圾或者根本无法使用这个值。

        那么全局变量为什么不需要赋初始值?

        我们之前讲过全局变量拥有独一无二的地址,如果不重新编译,那么无论运行多少次他都是那个地址,所以不用担心。

总结

        关于函数嵌套调用底层,其实就是重复函数调用的那几条指令,无非就是传参、调用、提升堆栈、操作、返回。主函数也是个普通的函数啊,所以主函数中调用了其他的函数,比如plus,这也是函数嵌套调用,只不过我们没有从主函数一开始就观察他的汇编代码,如果你去观察主函数的汇编代码,你会发现也就是那么几个动作,没什么太大的区别。

结语

        还是感谢大家的观看!另外,如果将的有错,请大佬一定指出。如果有听不懂的地方,也请问我。谢谢大家!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值