zoukankan      html  css  js  c++  java
  • 用栈传递参数

        这种技术和高级语言编译器的工作原理密切相关。我们下面结合 C 语言的函数调用,看一下用栈传递参数的思想。
        用栈传递参数的原理十分简单,就是由调用者将需要传递给子程序的参数压入栈中,子程序从栈中取得参数。我们看下面的例子。
    ;说明:计算( a – b ) ^ 3 ,a、b 为 word 型数据。
    ;参数:进入子程序时,栈顶存放IP,后面依次存放 a、b
    ;结果:( dx : ax ) = ( a – b ) ^ 3    ( 乘法,两个数都是8位,结果存放在AX中;如果两个数都是16位,高位存放在DX中,低位存放在AX中 )


    difcube : 
            push  bp
            mov  bp , sp
            mov  ax , [ bp + 4 ]   ; 将栈中a的值送入ax 中
            sub   ax , [ bp + 6 ]   ; 减栈中b的值
            mov  bp , ax
            mul  bp
            mul  bp
            pop  bp
            ret  4   ; call 返回,并恢复sp

    指令 ret  n 的含义用汇编语法描述为:
    pop  ip
    add  sp , n
    因为用栈传递参数,所以调用者在调用程序的时候要向栈中压入参数,子程序在返回的时候可以用ret  n 指令将栈顶指针修改为调用前的值。调用上面的子程序之前,需要压入两个参数。所以用ret  4 返回。


    我们看一下如何调用上面的程序,设 a = 3 、b = 1 ,下面的程序段计算:( a – b ) ^ 3:
    mov  ax , 1
    push  ax
    mov  ax , 3
    push  ax        ;注意参数压栈的顺序
    call  difcube
    我们看一下程序的执行过程中栈的变化:
    (1)假设栈的初始情况如下:
                                                                
    1000:0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00      
                                                          ↑ss:sp
    (2)执行以下指令:
    mov  ax , 1
    push  ax
    mov  ax , 3
    push  ax
    栈的情况变为:
                                               a     b          
    1000:0 00 00 00 00 00 00 00 00 00 00 00 00 03 00 01 00      
                                               ↑ss:sp           
    (3)执行指令: call  difcube
    栈的情况变为:
                                         IP    a     b          
    1000:0 00 00 00 00 00 00 00 00 00 00 XX XX 03 00 01 00      
                                         ↑ss:sp                 
    (4)执行指令: push  bp
    栈的情况变为:
                                   bp    IP    a     b          
    1000:0 00 00 00 00 00 00 00 00 XX XX XX XX 03 00 01 00      
                                   ↑ss:sp                       
    (5)执行指令: mov  bp , sp  ;ss:bp 指向 1000 : 8
                                   bp    IP    a     b          
    1000:0 00 00 00 00 00 00 00 00 XX XX XX XX 03 00 01 00      
                                   ↑ss:sp(ss:bp)                
    (6)执行以下指令:
    mov  ax , [ bp + 4 ] ; 将栈中a的值送入ax 中
    sub   ax , [ bp + 6 ] ; 减栈中b的值
    mov  bp , ax
    mul  bp
    mul  bp              ; DX:AX 中现在存放的是最终结果
    (7)执行指令: pop  bp
    栈的情况变为:
                                         IP    a     b          
    1000:0 00 00 00 00 00 00 00 00 XX XX XX XX 03 00 01 00      
                                         ↑ss:sp                 
    (8)执行指令: ret  4
    栈的情况变为:
                                                                
    1000:0 00 00 00 00 00 00 00 00 XX XX XX XX 03 00 01 00      
                                                          ↑ss:sp


    assume cs:code
    code segment
    start:
            mov ax , 1
            push ax
            mov ax , 3
            push ax
            call difcube
            mov ax , 4c00h
            int 21h
    difcube:
            push bp
            mov bp , sp
            mov ax , [bp+4]
            sub ax , [bp+6]
            mov bp , ax
            mul bp
            mul bp
            pop bp
            ret 4
    code ends
    end start
     // 最开始SS:SP=0769:0
    因为入栈后sp-2=FFFE
     // 这段空间才是真正的栈空间
     // 1和3入栈
     // call执行,IP,BP入栈
     // BP出栈
     
    // ret返回,sp恢复原来的0,结果=8,DX:AX=0000:0008


    下面我们通过一个C语言程序编译后的汇编语言程序,看一下栈在参数传递中的应用。要注意的是,在C语言中,局部变量也在栈中存储。

    C程序

    void  add ( int , int , int ) ;
    main ( )
    {
            int  a = 1 ;
            int  b = 2 ;
            int  c = 0 ;
            add ( a , b , c ) ;
            c++ ;
    }

    void  add ( int a , int b , int c ){
            c = a + b ;
    }
    编译后的汇编程序
    assume cs:code
    code segment
    start:
            mov bp , sp
            sub sp , 6
            mov word ptr [bp-6] , 0001        ; int a
            mov word ptr [bp-4] , 0002        ; int b
            mov word ptr [bp-2] , 0000        ; int c
            push [bp-2]
            push [bp-4]
            push [bp-6]
            call addr
            add sp ,6
            inc word ptr [bp-2]
    addr:       
            push bp
            mov bp , sp                ; bp 暂存刚入栈时候的sp,防止后面一些操作会改变sp,从而获取不到要得到的值;还有一种原因是先分配空间,sp就不改变了,只能通过bp来对空间再赋值
            mov ax , [bp+4]                ; a
            add ax , [bp+6]                ; b
            mov [bp+8] , ax                ; c
            mov sp , bp                ; 恢复入栈 sp
            pop bp                       
            ret                        ; 子程序返回,pop ip

    code ends
    end start
    // 加上了宏汇编开头两句和最后两句什么的,便于编译然后debug
    // 初始;SS:SP=0769:0000
                                                                   
    0769:FFF0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00      
                                                             ↑ss:sp
    // 进入main,定义变量a、b、c,开辟变量空间
                                                                   
    0769:FFF0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00      
                                            ↑ss:sp           ↑ss:bp
    // 开辟好空间,赋值
                                            a     b     c          
    0769:FFF0 00 00 00 00 00 00 00 00 00 00 10 00 20 00 00 00      
                                            ↑ss:sp           ↑ss:bp

    画的所有情况都是理想下的sp不更改的情况,但是实际上有些操作会修改sp,所以要用bp来保存初始的sp,然后通过bp加减法得到栈中想要访问的值

     // a、b、c 进栈

     // 再次push a、b、c 
     // IP进栈 IP=0020 , bp 进栈 bp=0000
     // mov [bp+8] , ax  ; c=3
     // pop bp,bp=0000
     // ret 返回,pop IP
     // c++,c=1
    // 进入子函数add(a , b , c),子函数要为a、b、c临时分配空间
                          a     b     c     a     b     c          
    0769:FFF0 00 00 00 00 10 00 20 00 00 00 10 00 20 00 00 00      
                          ↑ss:sp                             ↑ss:bp
    // 汇编call进入子程序,IP 进栈
                    IP    a     b     c     a     b     c          
    0769:FFF0 00 00 20 00 10 00 20 00 00 00 10 00 20 00 00 00      
                    ↑ss:sp                                   ↑ss:bp
    // bp 进栈
              bp    IP    a     b     c     a     b     c          
    0769:FFF0 00 00 20 00 10 00 20 00 00 00 10 00 20 00 00 00      
              ↑ss:sp                                         ↑ss:bp

              bp    IP    a     b     c     a     b     c          
    0769:FFF0 00 00 20 00 10 00 20 00 00 00 10 00 20 00 00 00      
              ↑ss:sp(bp)                                           
    // a+b=c,c=3
              bp    IP    a     b     c     a     b     c          
    0769:FFF0 00 00 20 00 10 00 20 00 30 00 10 00 20 00 00 00      
              ↑ss:sp(bp)                                           
    // pop bp,ret
                          a     b     c     a     b     c          
    0769:FFF0 00 00 20 00 10 00 20 00 30 00 10 00 20 00 00 00      
                          ↑ss:sp                             (bp)=0
    // sp+6,退出了子函数,释放空间
                                            a     b     c          
    0769:FFF0 00 00 20 00 10 00 20 00 30 00 10 00 20 00 00 00      
                                            ↑ss:sp           (bp)=0
    到这里子函数add(a , b , c)开辟的空间被释放
    // inc word ptr [bp-2]
                                            a     b     c          
    0769:FFF0 00 00 20 00 10 00 20 00 30 00 10 00 20 00 01 00      
                                            ↑ss:sp           (bp)=0
    // main函数结束,恢复sp=bp,释放所有开辟的空间
                                                                   
    0769:FFF0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00      
                                                             ↑ss:sp



  • 相关阅读:
    apue 第19章 伪终端
    apue 第18章 终端I/O
    linux IPC socket(2)
    linux IPC socket
    linux POSIX信号量
    【Luogu】【关卡2-16】线性动态规划(2017年10月)【还差三道题】
    【Luogu】【关卡2-15】动态规划的背包问题(2017年10月)【还差一道题】
    【Luogu】【关卡2-14】 树形数据结构(2017年10月)【AK】
    【Luogu】【关卡2-13】线性数据结构(2017年10月)【还差一道题】
    【Luogu】【关卡2-12】递推与递归二分(2017年10月)
  • 原文地址:https://www.cnblogs.com/meihao1203/p/7930960.html
Copyright © 2011-2022 走看看