栈基础知识

什么是栈

栈(Stack)是一种数据结构,属于线性数据结构,具有以下两个主要特点:

  1. 后进先出(LIFO, Last In First Out)

    • 栈的特点是“后进先出”,即最新被加入到栈的数据会最先被取出。这类似于现实中的一叠盘子,你总是从上面拿盘子,而不是从下面拿。
  2. 基本操作

    • 压栈(Push):将一个元素放入栈顶。
    • 弹栈(Pop):将栈顶的元素取出,同时移除它。
    • 查看栈顶元素(Peek 或 Top):查看当前栈顶的元素,但不移除它。

栈的结构

通常,栈被抽象为一个垂直的结构,只有一个端口可以进行操作(栈顶)。例如,假设有以下栈操作:

  • 初始栈:空
  • Push(1):栈 = [1]
  • Push(2):栈 = [1, 2]
  • Pop():栈 = [1](返回 2)
  • Peek():返回 1(栈不变)

栈的应用

栈在计算机科学中有许多重要的应用:

  1. 函数调用栈

    • 在程序执行过程中,每当一个函数被调用时,当前的执行状态会被压入栈中。当函数返回时,这些状态会从栈中弹出,恢复之前的执行环境。这就是为什么递归函数可以正确返回的原因。
  2. 表达式求值

    • 计算机可以使用栈来解析和求值数学表达式,特别是那些使用逆波兰表示法(RPN)的表达式。
  3. 撤销操作

    • 在编辑器等应用中,栈可以用来实现撤销操作:每次修改都被压入栈中,当用户选择撤销时,栈顶的操作被弹出并回退。
  4. 括号匹配

    • 栈常用于检查表达式中的括号是否匹配,例如在编译器中,用来检测代码中的括号是否成对。

实现方式

栈通常可以通过数组或链表来实现:

  • 数组实现:通过一个固定大小的数组,利用一个指针标记栈顶位置。
  • 链表实现:通过链表的头部插入和删除节点来实现栈操作。

栈是一个基础而又重要的数据结构,广泛应用于各种算法和系统设计中。

什么是栈帧

栈帧(Stack Frame) 是在函数调用过程中为该函数分配的栈上的一块内存区域。栈帧用于管理函数的局部数据、参数、返回地址和其他必要的信息,以便函数能够正确执行并在完成后返回调用点。每当一个函数被调用时,都会在栈上创建一个新的栈帧,函数返回时,该栈帧会被销毁。

栈帧的作用

  1. 存储返回地址:在函数调用时,当前执行位置的返回地址会被存储在栈帧中,以便函数执行完毕后能够返回到正确的位置继续执行。

  2. 管理函数参数:函数所需的参数通常会被存储在栈帧中,以便在函数执行过程中能够访问这些参数。

  3. 保存局部变量:函数内部定义的局部变量会被存储在栈帧中。栈帧的这一部分在函数执行期间有效,当函数结束时,这些局部变量的存储空间会被释放。

  4. 保存寄存器状态:在调用函数时,栈帧可以用于保存某些寄存器的值,确保在函数返回后寄存器的状态能够恢复到调用前的状态。

栈帧的结构

栈帧的具体结构可能因编程语言、编译器、操作系统和硬件架构的不同而有所变化,但通常包括以下部分:

  • 返回地址:存储调用函数的地址,确保函数执行完毕后能够返回到正确的位置。
  • 参数区域:用于存储传递给函数的参数。
  • 局部变量区域:用于存储函数的局部变量。
  • 保存的寄存器:用于保存函数调用前的一些寄存器值,以便函数返回时恢复这些寄存器。
  • 栈帧指针(Frame Pointer, FP):指向当前栈帧的固定位置,帮助程序在函数调用中管理栈帧。

栈帧的工作原理

当程序执行函数调用时,会在栈上为该函数分配一个栈帧。栈帧包含函数执行所需的所有信息,包括参数、局部变量和返回地址等。当函数执行完毕时,栈帧会被销毁,栈指针(Stack Pointer, SP)会被调整回到调用点,程序继续从返回地址处执行。

栈帧是现代计算机程序执行中非常重要的一个概念,理解栈帧有助于深入理解函数调用过程、调试程序以及进行安全性分析(如缓冲区溢出攻击的防御)。

栈帧的结构

栈帧(Stack Frame)是函数调用过程中在栈上分配的内存块,用于存储函数的局部数据和执行状态。每次函数调用时,系统会为该函数创建一个新的栈帧,函数返回时,该栈帧会被销毁。栈帧的结构通常包括以下几个主要部分:

栈帧的主要组成部分

  1. 返回地址

    • 位置:栈帧的顶部(或靠近栈顶的地方)。
    • 作用:存储函数调用后的下一条指令的地址。当函数执行完毕时,程序将控制权转移到这个地址,以继续执行调用函数后的代码。
  2. 函数参数

    • 位置:在返回地址下方。
    • 作用:存储传递给函数的参数。这些参数可以是值、指针等,取决于函数调用约定。
  3. 局部变量

    • 位置:在函数参数下方。
    • 作用:存储函数内部定义的局部变量。这些变量只在函数执行期间有效。
  4. 保存的寄存器

    • 位置:局部变量下方或栈帧的其他部分。
    • 作用:存储调用函数前的寄存器状态,以便函数返回时能恢复寄存器的值,确保程序的状态一致性。
  5. 栈帧指针(Frame Pointer, FP)

    • 位置:栈帧的一个固定位置,通常是函数入口处。
    • 作用:用于指向当前栈帧的起始位置,帮助访问栈帧内的各个部分。
  6. 动态链(Dynamic Link)

    • 位置:在保存的寄存器下方。
    • 作用:指向调用者的栈帧,帮助在函数调用链中进行返回。

栈帧结构示意

+------------------+
|  返回地址        |
+------------------+
|  函数参数        |
+------------------+
|  局部变量        |
+------------------+
|  保存的寄存器    |
+------------------+
|  栈帧指针        |
+------------------+
|  旧的栈帧指针    |
+------------------+
|  ...             |

每个栈帧的结构和大小可能会因编译器、操作系统和体系结构的不同而有所变化,但上述部分在大多数系统中都是常见的。栈帧的管理对程序的正常执行至关重要,了解其结构有助于理解函数调用、调试程序和进行安全分析。

传参方式

在x86和x64架构下,函数调用时参数的传递方式有所不同,特别是在栈上传递参数的方式上。这些差异主要体现在调用约定和寄存器的使用上。

x86架构(32位)

在x86架构中,函数调用通常使用栈来传递参数,具体方式取决于使用的调用约定。以下是常见的x86调用约定:

  1. cdecl(C Declaration)

    • 参数传递顺序:从右到左,将参数压入栈中。
    • 栈清理:调用者(Caller)负责清理栈上的参数。
    • 返回值:通过 EAX 寄存器返回结果。

    示例

    int sum(int a, int b) {
        return a + b;
    }
    
    // 调用时的栈布局:
    // push b
    // push a
    // call sum
    // add esp, 8  ; 调用者清理栈
    
  2. stdcall

    • 参数传递顺序:同样从右到左,将参数压入栈中。
    • 栈清理:被调用者(Callee)负责清理栈上的参数。
    • 返回值:通过 EAX 寄存器返回结果。

    示例

    int sum(int a, int b) {
        return a + b;
    }
    
    // 调用时的栈布局:
    // push b
    // push a
    // call sum
    // ; 被调用者清理栈
    
  3. fastcall

    • 参数传递顺序:前两个参数通过寄存器 ECX 和 EDX 传递,其余参数从右到左压入栈中。
    • 栈清理:被调用者清理栈。
    • 返回值:通过 EAX 寄存器返回结果。

    示例

    int sum(int a, int b, int c) {
        return a + b + c;
    }
    
    // 调用时的栈布局:
    // mov ecx, a
    // mov edx, b
    // push c  ; 通过栈传递
    // call sum
    // ; 被调用者清理栈
    

x64架构(64位)

在x64架构中,参数传递方式与x86有所不同。现代x64系统通常使用 Microsoft x64 或 System V AMD64 调用约定,参数主要通过寄存器传递,而不是栈。

  1. Microsoft x64 调用约定(Windows):

    • 前四个整数或指针参数:通过寄存器 RCXRDXR8, 和 R9 传递。
    • 额外参数:通过栈传递,从右到左依次压入栈中。
    • 浮点参数:通过 XMM0 到 XMM3 寄存器传递。
    • 栈清理:被调用者负责清理栈。
    • 返回值:通过 RAX 寄存器返回。

    示例

    int sum(int a, int b, int c, int d, int e) {
        return a + b + c + d + e;
    }
    
    // 调用时的布局:
    // mov rcx, a  ; 第1个参数
    // mov rdx, b  ; 第2个参数
    // mov r8, c   ; 第3个参数
    // mov r9, d   ; 第4个参数
    // push e      ; 额外参数通过栈传递
    // call sum
    
  2. System V AMD64 调用约定(Unix/Linux):

    • 前六个整数或指针参数:通过寄存器 RDIRSIRDXRCXR8, 和 R9 传递。
    • 额外参数:通过栈传递,从右到左依次压入栈中。
    • 浮点参数:通过 XMM0 到 XMM7 寄存器传递。
    • 栈清理:调用者负责清理栈。
    • 返回值:通过 RAX 和 RDX(如果需要返回两个值)寄存器返回。

    示例

    int sum(int a, int b, int c, int d, int e) {
        return a + b + c + d + e;
    }
    
    // 调用时的布局:
    // mov rdi, a  ; 第1个参数
    // mov rsi, b  ; 第2个参数
    // mov rdx, c  ; 第3个参数
    // mov rcx, d  ; 第4个参数
    // mov r8, e   ; 第5个参数
    // ; 若有额外参数,将其压入栈中
    // call sum
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值