zoukankan      html  css  js  c++  java
  • 用汇编语言研究C语言的全局变量、局部变量、参数、返回值放在哪里

    前提知识

      c0s调用main函数的地址:  11ah

      main函数的连接地址:  01fah

    一、全局变量与局部变量

    测试程序

    int a1,a2,a3;
    
    void f(void);
    void g(void);
    void h(void);
    main()
    {
        int b1,b2,b3;
        a1 = 0xa1;a2 = 0xa2;a3 = 0xa3;
        b1 = 0xb1;b2 = 0xb2;b3 = 0xb3;
    }
    
    void f(void)
    {
        int c1,c2,c3;
        a1 = 0x0fa1;a2 = 0x0fa2; a3 = 0x0fa3;
        c1 = 0xc1; c2 = 0xc2; c3 = 0xc3;
        c1 = c2 + c3;
    }
    
    void g(void)
    {
        int i = 100;
        while(i--);
    }
    
    void h(void)
    {
        int h1,h2,h3,h4,h5,h6,h7;
        h1 = 0xc1; h2 = 0xc2; h3 = 0xc3;h4 = 0xc4;
        h5 = 0xc5; h6 = 0xc6; h7 = 0xc7;
        h1 = h2 + h3;
        h2 = h3 + h4;
    }
    View Code

    编译、连接后,用debug调试这段代码,根据函数分别贴出对应的反汇编代码

    1、main函数

    (1)全局变量

    main()
    {
        int b1,b2,b3;
        a1 = 0xa1;a2 = 0xa2;a3 = 0xa3;
        b1 = 0xb1;b2 = 0xb2;b3 = 0xb3;
    }

    对应的反汇编代码

      可以看到全局变量,a1、a2、a3的地址分别是ds:[01a6]、ds:[01a8]、ds:[01aa]。

      可以看到,ds:[01a6]的物理地址是16266h,而程序的结束位置是CS:[2a0]的物理地址是15d60。可见,全局变量位于代码段外。ds=ss,而sp=ffe6,ss:sp的物理位置为260a6h,即栈顶位于260a6h,栈应高于栈顶。所以全局变量不可能位于栈区。

        综上所述,我认为全局变量位于非代码段,非栈段,而位于data段(初始化)或者bss段(未初始化)。

    (2)局部变量

     开辟在栈中的局部变量 

      a) 编译器先将BP压入栈

      b)用BP保存栈指针,然后SP-6,为局部变量开辟空间。

        push bp

        mov bp,sp

        sub sp,+6

      c) 函数返回前恢复栈,释放局部变量空间

        mov sp,bp

      d) 恢复BP

    2、f函数

    void f(void)
    {
        int c1,c2,c3;
        a1 = 0x0fa1;a2 = 0x0fa2; a3 = 0x0fa3;
        c1 = 0xc1; c2 = 0xc2; c3 = 0xc3;
        c1 = c2 + c3;
    }

      对应的反汇编代码

      局部变量c1、c2被开辟在寄存器SI、DI中,而c3则开辟在栈中。

      开辟在寄存器中的局部变量 

      a) 编译器先将BP压入栈

      b)用BP保存栈指针,将SI、DI压入栈,从而为局部变量开辟空间

        push bp

        mov bp,sp

        push si

        push di

      c) 函数返回前恢复寄存器(释放局部变量),然后恢复栈

        pop di

        pop si

        mov sp,bp

      d) 恢复BP

      无论开辟到栈中,还是开辟在寄存器中,栈指针的移动都是相同的。

    3、g函数

    void g(void)
    {
        int i = 100;
        while(i--);
    }

      对应的反汇编代码

      通过这个函数的反汇编可以重复验证“开辟在寄存器中的局部变量”的结论。

    4、h函数

     还会开辟到其他地方吗?

    void h(void)
    {
        int h1,h2,h3,h4,h5,h6,h7;
        h1 = 0xc1; h2 = 0xc2; h3 = 0xc3;h4 = 0xc4;
        h5 = 0xc5; h6 = 0xc6; h7 = 0xc7;
        h1 = h2 + h3;
        h2 = h3 + h4;
    }

     对应的反汇编代码

      可以看到,当有7个局部变量的情况下,局部变量除了开辟在栈中,就是寄存器中,没别的地方了

     什么时候开辟在栈中,什么时候开辟在寄存器中?这个问题还搞不懂,不过感觉没多大意义。

    二、函数如何传递参数,又是如何接受的呢

      测试程序

    void showchar(char a,char b);
    
    main()
    {
        showchar('a',2);
    }
    
    void showchar(char a,char b)
    {
        char r;
        r  = a;
        r  = b;
    }
    View Code

    1、main函数

    main()
    {
        showchar('a',2);
    }

    对应的反汇编代码

    a) 函数的参数是通过栈传递,而且是从右到左依次入栈

    b) 即使是char型变量,在传递参数时,也是占用两个字节,因为push操作是两个字节为单位的。取的时候,是按照它的类型来的

    c) 释放参数可以通过多次pop来实现。事实上,有时是通过“add sp,+数值”来实现的。显然,后者释放空间来的更快。

    2、showchar函数

    void showchar(char a,char b)
    {
        char r;
        r  = a;
        r  = b;
    }

    对应的反汇编代码

    d) 调用函数前后堆栈保持一致,也就是函数返回时要让堆栈指针恢复到和进入函数时一样的状态

    e) 函数接受形参是通过从栈中取的

    f) 对于为初始化的局部变量,编译器是不会给它赋初值的,而是拿来就用

    g) main函数的局部变量的寿命要比调用函数showchar的形参寿命长

    h) BP不仅保存了堆栈的值,而且通过它可以找到传入参数的值,BP+4是第一个参数,BP+6是第二个参数......取参数是从左到右取的(这就是堆栈的妙处)

    三、函数返回值

     测试程序

    char f(void);
    
    main()
    {
        char c;
        c = f();
    }
    
    char f(void)
    {
        return 'a';
    }
    View Code

    1、main函数

    char f(void);
    
    main()
    {
        char c;
        c = f();
    }

    对应的反汇编代码

    2、f函数

    char f(void)
    {
        return 'a';
    }

    对应的反汇编代码

    函数返回值:

      char型 AL

      int型 AX  

    四、总结

    1、全局变量

      全局变量位于非代码段,非栈段,而位于data段(初始化)或者bss段(未初始化)。

    2、局部变量

    a、开辟在堆栈中的局部变量,通过“mov sp,bp”来释放空间

    b、开辟在寄存器中的局部变量(push si/di),通过"pop si/di”来实现

    c、未初始化的局部变量,编译器并不会给它赋初值,而是拿来就用

    3、如何传递参数(主函数)

    a、函数的参数是通过栈传递,而且是从右到左依次入栈

    b、即使是char型变量,在传递参数时,也是占用两个字节,因为push操作是两个字节为单位的。

    c、showchar('a',2)这样的传入两个常数,也会在堆栈中开辟两个空间,也即对应两个实参变量。

    4、函数如何接收参数(子函数)

    a、 函数接受形参是通过从栈中取的

    b、通过BP可以找到传入参数的值,BP+4是第一个参数,BP+6是第二个参数......取参数是从左到右取的

    5、如何释放参数(主函数)

       释放参数可以通过多次pop来实现。事实上,有时是通过“add sp,+数值”来实现的。

    6、函数返回值

     char型 AL

     int型 AX  

    五、其他结论

    1、局部变量、传递参数和接收参数都与堆栈脱不了干系

      一个结论:局部变量、传递参数和接收参数都伴随着入栈、出栈、读堆栈中的内容等操作。对堆栈的这些操作,完成了变量和参数的创建、使用和释放。

      开辟在堆栈中的变量和参数都是在堆栈中开辟空间的,释放的时候也就通过移动sp或者pop指令来完成。

      开辟在寄存器中的变量,是通过“push si/di”来获取空间的,释放的时候是通过“pop si/di”来完成的。

    2、C语言函数汇编后的代码 

    push bp
    mov bp,sp
    ...
    ...
    mov sp,bp
    pop bp
    ret

       这些代码是每个函数反汇编都会出现的,怎样理解呢?

    a、BP保存了堆栈的值,所以可以利用堆栈来开辟局部变量的空间。(倘若不保存堆栈的值,将来怎样回收这些空间呢?可能会比较麻烦)

    b、通过BP可以找到传入参数的值,BP+4是第一个参数,BP+6是第二个参数......取参数是从左到右取的(这就是堆栈的妙处) 

    参考:王爽汇编语言综合研究-函数如何接收不定数量的参数

       王爽汇编语言综合研究-使用内存空间

       《汇编语言》319页研究实验3 “使用内存空间”

  • 相关阅读:
    Title
    Title
    Title
    Title
    Title
    Title
    Title
    Title
    Title
    git 的使用
  • 原文地址:https://www.cnblogs.com/amanlikethis/p/3570832.html
Copyright © 2011-2022 走看看