深入理解python虚拟机:程序执行的载体——栈帧
栈帧(Stack Frame)是 Python 虚拟机中程序执行的载体之一,也是 Python 中的一种执行上下文。每当 Python 执行一个函数或方法时,都会创建一个栈帧来表示当前的函数调用,并将其压入一个称为调用栈(Call Stack)的数据结构中。调用栈是一个后进先出(LIFO)的数据结构,用于管理程序中的函数调用关系。
栈帧的创建和销毁是动态的,随着函数的调用和返回而不断发生。当一个函数被调用时,一个新的栈帧会被创建并推入调用栈,当函数调用结束后,对应的栈帧会从调用栈中弹出并销毁。
栈帧的使用使得 Python 能够实现函数的嵌套调用和递归调用。通过不断地创建和销毁栈帧,Python 能够跟踪函数调用关系,保存和恢复局部变量的值,实现函数的嵌套和递归执行。同时,栈帧还可以用于实现异常处理、调试信息的收集和优化技术等。
需要注意的是,栈帧是有限制的,Python 解释器会对栈帧的数量和大小进行限制,以防止栈溢出和资源耗尽的情况发生。在编写 Python 程序时,合理使用函数调用和栈帧可以帮助提高程序的性能和可维护性。
栈帧数据结构
typedef struct _frame { | |
PyObject_VAR_HEAD | |
struct _frame *f_back; /* previous frame, or NULL */ | |
PyCodeObject *f_code; /* code segment */ | |
PyObject *f_builtins; /* builtin symbol table (PyDictObject) */ | |
PyObject *f_globals; /* global symbol table (PyDictObject) */ | |
PyObject *f_locals; /* local symbol table (any mapping) */ | |
PyObject **f_valuestack; /* points after the last local */ | |
/* Next free slot in f_valuestack. Frame creation sets to f_valuestack. | |
Frame evaluation usually NULLs it, but a frame that yields sets it | |
to the current stack top. */ | |
PyObject **f_stacktop; | |
PyObject *f_trace; /* Trace function */ | |
/* In a generator, we need to be able to swap between the exception | |
state inside the generator and the exception state of the calling | |
frame (which shouldn't be impacted when the generator "yields" | |
from an except handler). | |
These three fields exist exactly for that, and are unused for | |
non-generator frames. See the save_exc_state and swap_exc_state | |
functions in ceval.c for details of their use. */ | |
PyObject *f_exc_type, *f_exc_value, *f_exc_traceback; | |
/* Borrowed reference to a generator, or NULL */ | |
PyObject *f_gen; | |
int f_lasti; /* Last instruction if called */ | |
/* Call PyFrame_GetLineNumber() instead of reading this field | |
directly. As of 2.3 f_lineno is only valid when tracing is | |
active (i.e. when f_trace is set). At other times we use | |
PyCode_Addr2Line to calculate the line from the current | |
bytecode index. */ | |
int f_lineno; /* Current line number */ | |
int f_iblock; /* index in f_blockstack */ | |
char f_executing; /* whether the frame is still executing */ | |
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */ | |
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */ | |
} PyFrameObject; | 
内存申请和栈帧的内存布局
在 cpython 当中,当我们需要申请一个 frame object 对象的时候,首先需要申请内存空间,但是在申请内存空间的时候并不是单单申请一个 frameobject 大小的内存,而是会申请额外的内存空间,大致布局如下所示。

- f_localsplus,这是一个数组用户保存函数执行的 local 变量,这样可以直接通过下标得到对应的变量的值。
 - ncells 和 nfrees,这个变量和我们前面在分析 code object 的函数闭包相关,ncells 和 ncells 分别表示 cellvars 和 freevars 中变量的个数。
 - stack,这个变量就是函数执行的时候函数的栈帧,这个大小在编译期间就可以确定因此可以直接确定栈空间的大小。
 
下面是在申请 frame object 的核心代码:
Py_ssize_t extras, ncells, nfrees; | |
ncells = PyTuple_GET_SIZE(code->co_cellvars); // 得到 co_cellvars 当中元素的个数 没有的话则是 0 | |
nfrees = PyTuple_GET_SIZE(code->co_freevars); // 得到 co_freevars 当中元素的个数 没有的话则是 0 | |
// extras 就是表示除了申请 frame object 自己的内存之后还需要额外申请多少个 指针对象 | |
// 确切的带来说是用于保存 PyObject 的指针 | |
extras = code->co_stacksize + code->co_nlocals + ncells + | |
nfrees; | |
if (free_list == NULL) { | |
f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, | |
extras); | |
if (f == NULL) { | |
Py_DECREF(builtins); | |
return NULL; | |
} | |
} | |
// 这个就是函数的 code object 对象 将其保存到栈帧当中 f 就是栈帧对象 | |
f->f_code = code; | |
extras = code->co_nlocals + ncells + nfrees; | |
// 这个就是栈顶的位置 注意这里加上的 extras 并不包含栈的大小 | |
f->f_valuestack = f->f_localsplus + extras; | |
// 对额外申请的内存空间尽心初始化操作 | |
for (i=0; i<extras; i++) | |
f->f_localsplus[i] = NULL; | |
f->f_locals = NULL; | |
f->f_trace = NULL; | |
f->f_exc_type = f->f_exc_value = f->f_exc_traceback = NULL; | |
f->f_stacktop = f->f_valuestack; // 将栈顶的指针指向栈的起始位置 | |
f->f_builtins = builtins; | |
Py_XINCREF(back); | |
f->f_back = back; | |
Py_INCREF(code); | |
Py_INCREF(globals); | |
f->f_globals = globals; | |
/* Most functions have CO_NEWLOCALS and CO_OPTIMIZED set. */ | |
if ((code->co_flags & (CO_NEWLOCALS | CO_OPTIMIZED)) == | |
(CO_NEWLOCALS | CO_OPTIMIZED)) | |
; /* f_locals = NULL; will be set by PyFrame_FastToLocals() */ | |
else if (code->co_flags & CO_NEWLOCALS) { | |
locals = PyDict_New(); | |
if (locals == NULL) { | |
Py_DECREF(f); | |
return NULL; | |
} | |
f->f_locals = locals; | |
} | |
else { | |
if (locals == NULL) | |
locals = globals; | |
Py_INCREF(locals); | |
f->f_locals = locals; | |
} | |
f->f_lasti = -1; | |
f->f_lineno = code->co_firstlineno; | |
f->f_iblock = 0; | |
f->f_executing = 0; | |
f->f_gen = NULL; | 
现在我们对 frame object 对象当中的各个字段进行分析,说明他们的作用:
- PyObject_VAR_HEAD:表示对象的头部信息,包括引用计数和类型信息。
 - f_back:前一个栈帧对象的指针,或者为NULL。
 - f_code:指向 PyCodeObject 对象的指针,表示当前帧执行的代码段。
 - f_builtins:指向 PyDictObject 对象的指针,表示当前帧的内置符号表,字典对象,键是字符串,值是对应的 python 对象。
 - f_globals:指向 PyDictObject 对象的指针,表示当前帧的全局符号表。
 - f_locals:指向任意映射对象的指针,表示当前帧的局部符号表。
 - f_valuestack:指向当前帧的值栈底部的指针。
 - f_stacktop:指向当前帧的值栈顶部的指针。
 - f_trace:指向跟踪函数对象的指针,用于调试和追踪代码执行过程,这个字段我们在后面的文章当中再进行分析。
 - f_exc_type、f_exc_value、f_exc_traceback:这个字段和异常相关,在函数执行的时候可能会产生错误异常,这个就是用于处理异常相关的字段。
 - f_gen:指向当前生成器对象的指针,如果当前帧不是生成器,则为NULL。
 - f_lasti:上一条指令在字节码当中的下标。
 - f_lineno:当前执行的代码行号。
 - f_iblock:当前执行的代码块在f_blockstack中的索引,这个字段也主要和异常的处理有关系。
 - f_executing:表示当前帧是否仍在执行。
 - f_blockstack:用于try和loop代码块的堆栈,最多可以嵌套 CO_MAXBLOCKS 层。
 - f_localsplus:局部变量和值栈的组合,是一个动态大小的数组。
 
如果我们在一个函数当中调用另外一个函数,这个函数再调用其他函数就会形成函数的调用链,就会形成下图所示的链式结构。

例子分析
我们现在来模拟一下下面的函数的执行过程。
import dis | |
def foo(): | |
a = 1 | |
b = 2 | |
return a + b | |
if __name__ == '__main__': | |
dis.dis(foo) | |
print(foo.__code__.co_stacksize) | |
foo() | 
上面的 foo 函数的字节码如下所示:
6 0 LOAD_CONST 1 (1) | |
2 STORE_FAST 0 (a) | |
7 4 LOAD_CONST 2 (2) | |
6 STORE_FAST 1 (b) | |
8 8 LOAD_FAST 0 (a) | |
10 LOAD_FAST 1 (b) | |
12 BINARY_ADD | |
14 RETURN_VALUE | 
函数 foo 的 stacksize 等于 2 。
初始时 frameobject 的布局如下所示:

现在执行第一条指令 LOAD_CONST 此时的 f_lasti 等于 -1,执行完这条字节码之后栈帧情况如下:

在执行完这条字节码之后 f_lasti 的值变成 0。字节码 LOAD_CONST 对应的 c 源代码如下所示:
TARGET(LOAD_CONST) { | |
PyObject *value = GETITEM(consts, oparg); // 从常量表当中取出下标为 oparg 的对象 | |
Py_INCREF(value); | |
PUSH(value); | |
FAST_DISPATCH(); | |
} | 
首先是从 consts 将对应的常量拿出来,然后压入栈空间当中。
再执行 STORE_FAST 指令,这个指令就是将栈顶的元素弹出然后保存到前面提到的 f_localsplus 数组当中去,那么现在栈空间是空的。STORE_FAST 对应的 c 源代码如下:
TARGET(STORE_FAST) { | |
PyObject *value = POP(); // 将栈顶元素弹出 | |
SETLOCAL(oparg, value); // 保存到 f_localsplus 数组当中去 | |
FAST_DISPATCH(); | |
} | 
执行完这条指令之后 f_lasti 的值变成 2 。
接下来的两条指令和上面的一样,就不做分析了,在执行完两条指令,f_lasti 变成 6 。
接下来两条指令分别将 a b 加载进入栈空间单中现在栈空间布局如下所示:

然后执行 BINARY_ADD 指令 弹出栈空间的两个元素并且把他们进行相加操作,最后将得到的结果再压回栈空间当中。
TARGET(BINARY_ADD) { | |
PyObject *right = POP(); | |
PyObject *left = TOP(); | |
PyObject *sum; | |
if (PyUnicode_CheckExact(left) && | |
PyUnicode_CheckExact(right)) { | |
sum = unicode_concatenate(left, right, f, next_instr); | |
/* unicode_concatenate consumed the ref to left */ | |
} | |
else { | |
sum = PyNumber_Add(left, right); | |
Py_DECREF(left); | |
} | |
Py_DECREF(right); | |
SET_TOP(sum); // 将结果压入栈中 | |
if (sum == NULL) | |
goto error; | |
DISPATCH(); | |
} | 
最后执行 RETURN_VALUE 指令将栈空间结果返回。
总结
在本篇文章当中主要介绍了 cpython 当中的函数执行的时候的栈帧结构,这里面包含的程序执行时候所需要的一些必要的变量,比如说全局变量,python 内置的一些对象等等,同时需要注意的是 python 在查询对象的时候如果本地 f_locals 没有找到就会去全局 f_globals 找,如果还没有找到就会去 f_builtins 里面的找,当一个程序返回的时候就会找到 f_back 他上一个执行的栈帧,将其设置成当前线程正在使用的栈帧,这就完成了函数的调用返回,关于这个栈帧还有一些其他的字段我们没有谈到在后续的文章当中将继续深入其中一些字段。
相关文章:
深入理解python虚拟机:程序执行的载体——栈帧
栈帧(Stack Frame)是 Python 虚拟机中程序执行的载体之一,也是 Python 中的一种执行上下文。每当 Python 执行一个函数或方法时,都会创建一个栈帧来表示当前的函数调用,并将其压入一个称为调用栈(Call Stac…...
云服务器-Docker容器-系统搭建部署
一、引言 最近公司在海外上云服务器,作者自己也搞了云服务器去搭建部署系统,方便了解整体架构和系统的生命周期,排查解决问题可以从原理侧进行分析实验。虽然用的云不是同一个,但是原理都是相通的。 二、选型 作者选用的是腾讯云…...
ES 索引重命名--Reindex(一)
ES reindex脚本流程,下图为整体流程: 步骤(1):每次写入把之前的索引删除再重新创建索引,然后判断索引是否创建成功,由于创建成功返回结果是json,因此用Json Input插件去解析json获得…...
Spring之bean的生命周期
目录 1.Bean的初始化过程 1.1代码详解 1.2思考 2.Bean的单例与多例选择 2.1论证单例与多例优缺点 2.2论证初始化时间点 2.3个例演示 Spring Bean的生命周期: 一、通过XML、Java annotation(注解)以及Java Configuration(配置类),等方式…...
策略梯度方法
策略梯度方法 数学背景 给定一个标量函数 J ( θ ) J\left(\theta\right) J(θ),利用梯度上升法,使其最大化,此时的 π θ \pi_\theta πθ就是最优策略。 θ t 1 θ t α ∇ θ J ( θ t ) \theta_{t1}\theta_t\alpha \nabla_\theta…...
博客系统之单元测试
对博客系统进行单元测试 1、测试查找已存在的用户 测试名称 selectByUsernameTest01 测试源码 //查找用户,存在 Test public void selectByUsernameTest01 () { UserDao userDao new UserDao(); String ret1 userDao.selectByUsername("张三").toStr…...
【ARM v8】如何在ARM上实现x86的rdtsc()函数
博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持! 博主链接 本人就职于国际知名终端厂商,负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。 博客…...
redis--事务
redis事务 在Redis中,事务是一组原子性操作的集合,它们被一起执行,要么全部执行成功,要么全部回滚。虽然Redis的事务并不遵循传统数据库的ACID特性,但它仍然提供了一种将多个命令打包成一组执行的机制,适用…...
111. 二叉树的最小深度
111. 二叉树的最小深度 给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明:叶子节点是指没有子节点的节点。 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeN…...
SpringMVC归纳与总结
前言 Spring的核心是IOC,一种依赖反转的解耦思想。MVC是一种处理Web请求的架构模式,当两者的作用结合,就形成了SpringMVC。 组成及运行原理 1. 两次映射 2. 为什么用适配器模式 过滤器与拦截器 1. 范围 静态资源与动态资源2. 生命周期…...
Python学习笔记_进阶篇(三)_django知识(二)
本章内容 Django model Model 基础配置 django默认支持sqlite,mysql, oracle,postgresql数据库。 <1> sqlite django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 引擎名称:django.db.backends.sqlite3 <2>mysql …...
RISC-V 整型通用寄存器介绍
简介 RISC-V64位/32位提供了32个整型通用寄存器,编号是x0~x31,这些整型通用寄存器的宽度与架构位数一致。 浮点数寄存器与整形寄存器一样也提供了32个:f0~f31,位数与架构位数一致。 通用寄存器介绍 零寄存器 x0/zero x0寄存…...
学习Vue:【性能优化】异步组件和懒加载
在Vue.js应用开发中,性能优化是一个至关重要的主题,而异步组件和懒加载是提升性能的有效方法之一。本文将介绍什么是异步组件和懒加载,以及如何在Vue.js中应用这些技术来提升应用性能。 异步组件和懒加载 异步组件 异步组件是指在需要的时候…...
pdf格式文件下载不预览,云存储的跨域解决
需求背景 后端接口中返回的是pdf文件路径比如: pdf文件路径 (https://wangzhendongsky.oss-cn-beijing.aliyuncs.com/wzd-test.pdf) 前端适配是这样的 <ahref"https://wangzhendongsky.oss-cn-beijing.aliyuncs.com/wzd-test.pdf&…...
httplib + nlohmann::json上传数据时中文乱码解决
1、nlohmann::json 1.1 编码格式使用UTF-8 参考 nlohmann::json 中文乱码解决方案 (1)将数据先转为UTF-8格式 2、httplib 2.1 上传数据前 (1)调用httplib::Response对象的set_header()方法来设置编码格式 httplib::Response res…...
JavaScript中的设计模式之一--单例模式和模块
虽然有一种疯狂天才的感觉可能很诱人,但重新发明轮子通常不是设计软件的最佳方法。很有可能有人已经遇到了和你一样的问题,并以一种聪明的方式解决了它。这样的最佳实践在形式化后被称为设计模式。今天我们来看看它们的概念,并检查单例模式和…...
回归预测 | MATLAB实现GAM广义加性模型多输入单输出回归预测(多指标,多图)
回归预测 | MATLAB实现GAM广义加性模型多输入单输出回归预测(多指标,多图) 目录 回归预测 | MATLAB实现GAM广义加性模型多输入单输出回归预测(多指标,多图)效果一览基本介绍程序设计参考资料 效果一览 基本…...
css学习4(背景)
1、CSS中,颜色值通常以以下方式定义: 十六进制 - 如:"#ff0000"RGB - 如:"rgb(255,0,0)"颜色名称 - 如:"red" 2、background-image 属性描述了元素的背景图像. 默认情况下,背景图像进…...
二、SQL,如何实现表的创建和查询
1、新建表格(在当前数据库中新建一个表格): (1)基础语法: create table [表名]( [字段:列标签] [该列数据类型] comment [字段注释], [字段:列标签] [该列数据类型] comment [字段注释], ……,…...
大数据及软件教学与实验专业实训室建设方案
一 、系统概述 大数据及软件教学与实验大数据及软件教学与实验在现代教育中扮演重要角色,这方面的教学内容涵盖了大数据处理、数据分析、数据可视化和大数据应用等多个方面。以下是大数据及软件教学与实验的一般内容:1. 数据基础知识:教授学生…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
