🛤️Go 传参和返回值是通过 FP+offset 实现
type
status
date
slug
summary
tags
category
icon
password
Status
Go 传参和返回值是通过 FP+offset 实现,并且存储在调用函数的栈帧中。
这句话描述了 Go 语言在底层(通常是汇编层面)如何处理函数调用时的参数传递和返回值返回,强调它们在内存(具体是函数调用栈)中的布局方式。
  1. FP (Frame Pointer - 帧指针):
      • 是什么: FP 是一个 CPU 寄存器。在函数调用过程中,它扮演着一个重要的角色,用来帮助管理当前正在执行的函数的栈帧(Stack Frame)
      • 作用: FP 通常指向当前函数栈帧中的一个固定位置。这个位置通常是栈帧的基址(Base Address),或者是栈帧中某个预先定义好的点(比如保存旧 FP 值的位置之后)。
      • 为什么需要 FP: 函数在执行时,会使用栈来存储局部变量、参数、返回地址等。栈顶指针(SP - Stack Pointer)会随着数据的压入(push)和弹出(pop)而不断移动。FP 提供了一个稳定的参考点,使得无论 SP 如何变化,函数都可以通过 FP + 某个固定的偏移量 (offset) 来可靠地访问到它的参数、局部变量以及为返回值预留的空间。
      • 关于“指向一个函数栈的顶部”的描述: 这句话可能有点歧义。"Top" 在栈的语境下有时指最低地址(栈向低地址增长时的栈顶),有时指最高地址(栈底)。更标准的说法是,FP 指向当前栈帧的基址或一个固定基准点。SP 才通常指向动态变化的栈顶(最低地址)。但这里的核心思想是 FP 提供了一个不变的基准
  1. FP+offset (帧指针 + 偏移量):
      • 一旦 FP 确定了当前栈帧的基准位置,那么栈帧内的其他所有数据(传递给这个函数的参数、这个函数内部的局部变量、以及这个函数需要返回给调用者的值的存储空间)都可以通过相对于 FP 的一个固定偏移量来访问。
      • 例如,编译器在编译时会计算好:第一个参数在 FP + 8 的位置,第二个参数在 FP + 16 的位置,第一个局部变量在 FP - 8 的位置,为第一个返回值预留的空间在 FP + 24 的位置等等(具体的偏移量取决于架构、调用约定和数据类型大小)。
      • 所以,“通过 FP+offset 实现”意味着访问这些数据是通过“基准点 FP 加上一个编译时算好的固定距离”这种方式来定位内存地址的。
  1. 存储在调用函数的栈帧中 (Stored in the Caller's Stack Frame):
      • 这是理解 Go 多返回值实现的关键!
      • 调用函数 (Caller): 发起函数调用的那个函数。
      • 被调用函数 (Callee): 实际执行并返回值的那个函数。
      • Go 的做法:调用函数 (Caller) 准备调用另一个函数 (Callee) 之前,它会在 自己的栈帧 (Caller's stack frame) 上预留出足够的空间来存放 Callee 即将返回的所有值。
      • 传递信息: Caller 会(通常通过栈或寄存器)告知 Callee 这些预留空间的位置。
      • Callee 的工作: 当 Callee 计算出返回值后,它直接将这些返回值写入到 Caller 事先在 Caller 栈帧里准备好的那些内存位置。它使用 Callee 自己的 FP 加上特定的偏移量(或者通过 Caller 传递过来的地址指针)来找到这些位置并写入数据。
      • 优点: 当 Callee 执行完毕,它自己的栈帧会被销毁,但由于返回值已经写入了 Caller 的栈帧,这些值对于 Caller 来说是安全和可访问的。这种方式天然地支持返回多个值,因为 Caller 只需要预留足够大的连续空间即可,Callee 依次填入就好。
总结解释这句话:
Go 语言在底层实现函数调用时,利用 CPU 的帧指针寄存器(FP)作为当前函数栈帧的一个稳定基准点。无论是传递给函数的参数,还是函数要返回的值,它们在内存中的位置都是通过这个基准点(FP)加上一个编译时确定的固定偏移量(offset)来访问的。特别地,对于函数的返回值(尤其是多个返回值),调用者(Caller) 会在 自己 的栈帧上预先分配好存储空间,而被调用者(Callee)则负责将计算出的结果直接写入到调用者准备好的这块内存区域中。这种机制使得 Go 可以简洁高效地实现多返回值功能。
 
后端系统容灾与高可用设计指南宝宝床边故事集:存储引擎
Loading...