zoukankan      html  css  js  c++  java
  • [转] 函数调用的栈分配

    当调用(call)一个函数时,主调函数将声明中的参数表以逆序压栈,然后将当前的代码执行指针(eip)压栈,跳转到被调函数的入口点。
            进入被调函数时,函数将esp减去相应字节数获取局部变量存储空间。被调函数返回(ret)时,将esp加上相应字节数,归还栈空间,弹出主调函数压在栈中的代码执行指针(eip),跳回主调函数。再由主调函数恢复到调用前的栈。
          
    为了访问函数局部变量,必须有方法定位每一个变量。变量相对于栈顶esp的位置在进入函数体时就已确定,但是由于esp会在函数执行期变动,所以将esp
    的值保存在ebp中,并事先将原ebp的值压栈保存,以声明中的顺序(即压栈的相反顺序)来确定偏移量。
    访问函数的局部变量和访问函数参数的区别:
    局部变量总是通过将ebp减去偏移量来访问,函数参数总是通过将ebp加上偏移量来访问。对于32位变量而言,第一个局部变量位于ebp-4,第二个位于ebp-8,以此类推,32位局部变量在栈中形成一个逆序数组;第一个函数参数位于ebp+8,第二个位于ebp+12,以此类推,32位函数参数在栈中形成一个正序数组。

          
    函数的返回值不同于函数参数,可以通过寄存器传递。如果返回值类型可以放入32位变量,比如int、short、char、指针等类型,将通过eax寄存
    器传递。如果返回值类型是64位变量,如_int64,则通过edx+eax传递,edx存储高32位,eax存储低32位。如果返回值是浮点类型,如
    float和double,通过专用的浮点数寄存器栈的栈顶返回。如果返回值类型是struct或class类型,编译器将通过隐式修改函数的签名,以引
    用型参数的形式传回。由于函数返回值通过寄存器返回,不需要空间分配等操作,所以返回值的代价很低。基于这个原因,C89规范中约定,不写明返回值类型的
    函数,返回值类型默认为int。这一规则与现行的C++语法相违背,因为C++中,不写明返回值类型的函数返回值类型为void,表示不返回值。这种语法
    不兼容性是为了加强C++的类型安全,但同时也带来了一些代码兼容性问题。
    代码示例
    VarType Func (Arg1, Arg2, Arg3, ... ArgN)
    {
        VarType Var1, Var2, Var3, ...VarN;
        //...
        return VarN;
    }
    假设sizeof(VarType) = 4(DWORD), 则一次函数调用汇编代码示例为:
    调用方代码:

    push ArgN ; 依次逆序压入调用参数
    push ...
    push Arg1
    call Func_Address ; 压入当前EIP后跳转
    跳转至被调方代码:
    push ebp ; 备份调用方EBP指针
    mov ebp, esp ; 建立被调方栈底
    sub esp, N * 4; 为局部变量分配空间
    mov dword ptr[esp - 4 * 1 ], 0 ; 初始化各个局部变量 = 0 这里假定VarType不是类
    mov dword ptr[esp - 4 * ... ], 0
    mov dword ptr[esp - 4 * N ], 0
    . . . . . . ; 这里执行一些函数功能语句(比如将第N个参数[ebp + N * 4]存入局部变量), 功能完成后将函数返回值存至eax
    add esp, N * 4 ; 销毁局部变量
    mov esp, ebp ; 恢复主调方栈顶
    pop ebp ; 恢复主调方栈底
    ret ; 弹出EIP 返回主调方代码
    接上面调用方代码:
    add esp, N * 4 ; 释放参数空间, 恢复调用前的栈
    mov dword ptr[ebp - 4], eax ; 将返回值保存进调用方的某个VarType型局部变量
    进入函数时堆栈分配示意图
    内存低地址 | ESP - - - - - - - - - - - - - - - - EBP - - - - - - - - - - - - - - - - - - - - - >| 内存高地址
    Stack State:VarN . . . Var3 Var2 Var1 SFP EIP Arg1 Arg2 Arg3 . . . ArgN
    //资料
    区...............................................................................................................................
    SFP 解释:      
    除了堆栈指针(ESP指向堆栈顶部的的低地址)之外,
    为了使用方便还有指向帧内固定地址的指针叫做帧指针(FP)。有些文章把它叫做局部基指针(LB-local base
    pointer)。从理论上来说, 局部变量可以用SP加偏移量来引用。 然而, 当有字被压栈和出栈后, 这些偏移量就变了。
    尽管在某些情况下编译器能够跟踪栈中的字操作, 由此可以修正偏移量, 但是在某些情况下不能。而且在所有情况下, 要引入可观的管理开销。
    而且在有些机器上, 比如Intel处理器, 由SP加偏移量访问一个变量需要多条指令才能实现。
            因此,
    许多编译器使用第二个寄存器, FP, 对于局部变量和函数参数都可以引用, 因为它们到FP的距离不会受到PUSH和POP操作的影响。
    在Intel CPU中, BP(EBP)用于这个目的。 在Motorola CPU中,
    除了A7(堆栈指针SP)之外的任何地址寄存器都可以做FP。考虑到我们堆栈的增长方向, 从FP的位置开始计算, 函数参数的偏移量是正值,
    而局部变量的偏移量是负值。
          
    当一个例程被调用时所必须做的第一件事是保存前一个FP(这样当例程退出时就可以恢复这个被保存的FP称为SFP)。 然后它把SP复制到FP,
    创建新的FP, 把SP向前移动为局部变量保留空间。 这称为例程的序幕(prolog)工作。当例程退出时, 堆栈必须被清除干净,
    这称为例程的收尾(epilog)工作。 Intel的ENTER和LEAVE指令, Motorola的LINK和UNLINK指令,
    都可以用于有效地序幕和收尾工作。
             所有局部变量都在栈中由函数统一分配,形成了类似逆序数组的结构,可以通过指针逐一访问。这一特点具有很多有趣性质,比如,考虑如下函数,找出其中的错误及其造成的结果:
    void f()
    {
    int i,a[10];
    for(i=0;i}
            这个函数中包含的错误,即使是C++新手也很容易发现,这是老生常谈的越界访问问
    题。但是这个错误造成的结果,是很多人没有想到的。这次的越界访问,并不会像很多新手预料的那样造成一个“非法操作”消息,也不会像很多老手估计的那样会
    默不作声,而是导致一个死循环。
            错误的本质显而易见,我们访问了a[10],但是a[10]并不存在。C++标准对于越界访问只是说“未定义操作”。我们知道,a[10]是数组a所在位置之后的一个位置,但问题是,是谁在这个位置上。是i!
          
    根据前面的讨论,i在数组a之前被声明,所以在a之前分配在栈上。但是,I386上栈是向下增长的,所以,a的地址低于i的地址。其结果是在循环的最后,
    a引用到了i自己!接下来的事情就不难预见了,a,也就是i,被重置为0,然后继续循环的条件仍然成立……这个循环会一直继续下去,直到在你
    的帐单上产生高额电费,直到耗光地球电能,直到太阳停止燃烧……呵呵,或者直到聪明的你把程序Kill了……
                   
                   
                   

    本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/92446/showart_1820202.html

  • 相关阅读:
    some tips
    ORA00847: MEMORY_TARGET/MEMORY_MAX_TARGET and LOCK_SGA cannot be set together
    Chapter 01Overview of Oracle 9i Database Perfomrmance Tuning
    Chapter 02Diagnostic and Tuning Tools
    变量与常用符号
    Chapter 18Tuning the Operating System
    标准输入输出
    Trace files
    DBADeveloped Tools
    Chapter 03Database Configuration and IO Issues
  • 原文地址:https://www.cnblogs.com/hewei2012/p/3171865.html
Copyright © 2011-2022 走看看