1.Lua字节码文件结构及加载过程
2.Lua5.4 源码剖析——虚拟机2 之 闭包与UpValue
3.计算机软件开发中扩展性语言有哪些?
4.探索 Lua5.2 内部实现:编译系统(1) 概述
5.程序开发中遇到的闭包源闭包lua语言概念是什么呢?
6.《Lua5.4 源码剖析——基本数据类型 之 Function》
Lua字节码文件结构及加载过程
在探索Lua的世界中,字节码文件结构与加载过程是码实程序运行效率的关键。本文将为你揭示Lua 5.4.3的原理内部奥秘,从文件头到函数块,闭包源闭包逐一剖析其构造与加载流程。码实让我们一起深入理解Lua的原理youcompleteme源码实现加载逻辑,通过luaU_undump函数,闭包源闭包将源代码编织成二进制的码实指令织锦。
首先,原理Lua字节码文件由文件头和函数块两大部分组成,闭包源闭包如同编织的码实经纬线,共同构建起程序的原理基石。文件头包含了Lua的闭包源闭包签名("\x1bLua")、版本号(例如Lua 5.4.3的码实)、官方格式号(0)以及LUAC_DATA的原理校验信息。紧接着是指令、整型和浮点型大小的指示,每个部分都承载着特定的含义,确保文件的正确性。
深入解析luaU_undump函数,它是Lua加载阶段的灵魂,处理着二进制文件的字节码。这个函数首先会进行头检查,包括Lua签名、版本号、格式号等,随后是lua_Integer和lua_Number的长度指示。例如,Lua 5.4.3的头字节包含整型、浮点型校验和文件头信息,通过反编译,我们可以看到函数原型的细节,如函数名、参数、行数和指令数量等。
在Lua 5.4.3的loadFunction函数中,我们看到函数块被精细划分,包括源代码、行号、参数、可变参数、短信php源码栈大小、字节码、常量、上值和闭包等元素。这些元素通过loadStringN、loadUnsigned等函数逐一加载,确保每个部分都按照特定规则组织。
文件结构的关键部分包括loadConstants(如main函数中的常量"a")、loadUpvalues(如全局表"_ENV"的加载)、loadProtos(函数内部原型的加载),以及loadDebug信息,如行号和变量名称。比如,main函数的4个指令和a函数的5个,以及upvalues数据" 5f 4e",都在这个过程中得到解析。
在Lua的实现中,lauxlib.c、lapi.c、ldo.c和lundump.c等核心文件,以及lua_load、f_parser、luaU_undump、checkHeader和loadFunction等核心函数,共同构建起字节码的编译和加载流程。从源代码到二进制,再到虚拟机的执行,每一个环节都经过精心设计,以达到高效的运行效率。
总的来说,Lua字节码文件结构的复杂性映射出其内在的执行效率。理解这些细节,不仅有助于我们编写更优化的脚本,也让我们对Lua的底层机制有了更深的认识。让我们继续探索luaU_undump的更多奥秘,揭开Lua字节码加载过程的神秘面纱。
Lua5.4 源码剖析——虚拟机2 之 闭包与UpValue
故事将由我们拥有了一段 Lua 代码开始,我们先用 Lua 语言写一段简单的打印一加一计算结果的 Lua 代码,并把代码保存在 luatest.lua 文件中:
可执行的一个 Lua 文件或者一份单独的文本形式 Lua 代码,在 Lua 源码中叫做 "Chunk"。外卖骑手源码无论我们通过什么形式去执行,或者用什么编辑器去执行,最终为了先载入这段 Lua 的 Chunk 到内存中,无外乎会归结到以下两种方式:1)Lua 文件的载入:require 函数 或 loadfile 函数;2)Lua 文本代码块的载入:load 函数;这两种方式最终都会来到下面源码《lparse.c》luaY_parser 函数。该函数是解析器的入口函数,负责完成代码解析工作,最终会创建并返回一个 Lua 闭包(LClosure),见下图的红框部分:
另外,上图中间有一行代码最终会调用到 statement 函数,statement 函数是 Chunk 解析的核心函数,它会一个一个字符地处理我们编写的 Lua 代码,完成词法分析和语法分析工作,想要了解字符处理整个状态流程的可以自行研读该部分源码,见源码《lparse.c》statement 函数部分代码:
完成了解析工作之后,luaY_parser 函数会把解析的所有成果放到 Lua 闭包(LClosure)对象之中,这些存储的内容能保证后续执行器能正常执行 Lua 闭包对应的代码。
Lua 闭包由 Proto(也叫函数原型)与 UpValue(也叫上值)构成,见源码《lobject.h》LClosure 定义,我们下面将进行详细的讲解:
UpValue 是 Lua 闭包数据相关的,在 Lua 的函数调用中,根据数据的作用范围可以把数据分为两种类型:1)内部数据:函数内部自己定义的数据,或者通过函数参数的形式传入的数据(在 Lua 中通过参数传入的数据本质上也是先赋值给一个局部变量);2)外部数据:在函数的更外层进行定义,脱离了该函数后仍然有效的数据;外部数据在我们的 Lua 闭包中就是 UpValue,也叫上值。
既然 Lua 支持函数嵌套,也知道了 UpValue 本质就是上层函数的内部数据。那么 UpValue 有必要存储于 Lua 闭包(LClosure)结构体当中吗?是为了性能考虑而做的一层指针引用缓存吗?回答:并不是基于性能的考虑,因为在实际的 Lua 运用场景中,函数嵌套的层数通常来说不会太多,个别函数多一层的查询访问判断不会带来过多的性能开销。需要在闭包当中存储 UpValue 主要原因是因为内存。Lua 作为一门精致小巧的脚本语言,设计初衷不希望占用过多的系统内存,它会尽量及时地清理内存中用不到的对象。在嵌套函数中,内层函数如果仍然有被引用处于有效状态,而外层函数已经没有被引用了已经无效了,此时 Lua 支持在保留内层函数的情况下,对外层函数进行清除,从而可以清理掉外层函数引用的押注系统源码非当前函数 UpValue 用途以外的大量数据内存。
尽管外层函数被清除了,Lua 仍然可以保持内层函数用到的 UpValue 值的有效性。UpValue 如何能继续保持有效,我们在之前的基础教程《基本数据类型 之 Function》里面学习过,主要是因为 UpValue 有 open 与 close 两种状态,当外层函数被清除的时候,UpValue 会有一个由 open 状态切换到 close 状态的过程,会对数据进行一定的处理,感兴趣的同学可以回到前面复习一下。
UpValue 有效性例子
接下来我们举一个代码例子与一个图例,表现一下 UpValue 在退出外层函数后仍然生效的情况,看一下可以做什么样的功能需求,加深一下印象,请看代码与注释:
上述代码在执行 OutFunc 函数后,外层的 globalFunc 函数变量完成了赋值,每次对它进行调用,都将可以对它引用的 UpValue 值即 outUpValue 变量进行正常加 1。
函数的内部数据属于函数自身的内容,外部其它函数无法通过直接的方式访问其它函数的内部数据。函数自身的东西会存在于 LClosure 结构体的 Proto*p 字段中。Proto 全称 "Function Prototypes",通常也可以叫做 "函数原型",我们来看一下它的定义,见源码《lobject.h》Proto 结构体:
结构体字段比较多,我们先不细看,后面用到哪个字段会再进行补充说明。函数的内部数据分为常量与变量(即函数局部变量),分别对应上图的如下字段:
1)常量:TValue* k 为指针指向常量数组;int sizek 为函数内部定义的常量个数,也即常量数组 k 的元素个数。
2)局部变量:LocVar* locvars 为指针指向局部变量数组;int sizelocvars 为函数定义的局部变量个数,也即局部变量数组 locvars 的元素个数。
UpValue 的描述信息会存储在 Proto 结构体中的 Upvaldesc* upvalues 字段,解析器解析 Lua 代码的时候会生成这个 UpValue 描述信息,并用于生成指令,而执行器运行的时候可以通过该描述信息方便快速地构建出真正的 UpValue 数组。
至此,我们知道了函数拥有 UpValue,有常量,有局部变量。外部数据 UpValue 也讲完,源码郝茗内部数据也讲完。接下来,我们开始学习函数运行的逻辑指令相关内容。
函数逻辑指令存储于函数原型 Proto 结构体中,这些函数逻辑是由一行行的 Lua 代码构成的,代码会被解析器翻译成 Lua 虚拟机能识别的指令,我们把这些指令称为 "OpCode",也叫 "操作码"。Proto 结构体存储 OpCode 使用的是下图中红框部分字段,见源码《lobject.h》Proto 结构体:
至此,我们可以简单提前说一下 Lua 虚拟机的功能了,本质上来看,Lua 虚拟机的工作,就是为当前函数(或者当前一段 OpCode 数组)准备好数据,然后有序执行 OpCode 指令。
对 OpCode 有了一定的认识了,接下来我们要补充一个 OpCode 相关的 Lua 闭包相关的内容,就是 Lua 闭包的运行环境。
一个 Lua 文件在载入的时候会先创建出一个最顶层(Top level)的 Lua 闭包,该闭包默认带有一个 UpValue,这个 UpValue 的变量名为 "_ENV",它指向 Lua 虚拟机的全局变量表,即_G 表,可以理解为_G 表即为当前 Lua 文件中代码的运行环境 (env)。事实上,每一个 Lua 闭包它们第一个 UpValue 值都是_ENV。
ENV 的定义在我们之前提到的解析器相关函数 mainfunc 中,见源码《lparser.c》:
如果想要设置这个载入后的初始运行环境不使用默认的 _G 表,除了直接在该文件代码中重新赋值_ENV 变量这种粗暴且不推荐的方式以外,通常是通过我们前面提到的加载 Lua 文件函数或加载 Lua 字符串代码函数传入 env 参数(Table 类型),就可以用自定义的 Table 作为当前 Lua 闭包的全局变量环境了,env 参数为上面两个函数的最末尾一个参数,'[' 与 ']' 字符中的内容表示参数可选,函数的定义摘自 Lua5.4 官网文档:
所以我们可以在 Lua 代码通过 _ENV 访问当前环境:
在 Lua 的旧版本中,变量的查询最多会分为 3 步:1)先从函数局部变量中进行查找;2)找不到的话就从 UpValue 中查找;3)还找不到就从全局环境默认 _G 表查找。而在 Lua5.4 中,把 UpValue 与全局 _G 表的查询统一为 UpValue 查询,并把一些操作判断提前到了解析器解析阶段进行,例如函数内部使用的某个 UpVaue 变量在代码解析的时候就可以通过 UpValue 描述信息知道存储于 Lua 闭包 upvals 数组的哪个下标位置,在执行器运行的时候只需要直接在数组拿取对应下标的这个 UpValue 数据即可。
从 OpCode 的层面来看,Lua 除了支持通过一个 UpValue 数组下标访问一个 UpValue 变量,在把 _G 表合并到 UpValue 之后,Lua 为此实现了通过一个字符串 key 值从某个 Table 类型的 UpValue 中查询变量的操作。
至此,我们了解了 Lua 闭包的结构与运行环境,以及 OpCode 的基本概念。接下来,我们将深入学习 OpCode,掌握 OpCode 就掌握了整个 Lua 虚拟机数据与逻辑的流向。
计算机软件开发中扩展性语言有哪些?
程序开发中扩展语言有很多,比如lua程序设计。1.Lua 是一门扩展式程序设计语言,被设计成支持通用过程式编程,并有相关数据描述设施。 同时对面向对象编程、函数式编程和数据驱动式编程也提供了良好的支持。 它作为一个强大、轻量的嵌入式脚本语言,可供任何需要的程序使用。 Lua 由 clean C(标准 C 和 C++ 间共通的子集) 实现成一个库。
2.作为一门扩展式语言,Lua 没有 "main" 程序的概念:它只能 嵌入 一个宿主程序中工作, 该宿主程序被称为 被嵌入程序 或者简称 宿主 。 宿主程序可以调用函数执行一小段 Lua 代码,可以读写 Lua 变量,可以注册 C 函数让 Lua 代码调用。 依靠 C 函数,Lua 可以共享相同的语法框架来定制编程语言,从而适用不同的领域。 Lua 的官方发布版包含一个叫做 lua 的宿主程序示例, 它是一个利用 Lua 库实现的完整独立的 Lua 解释器,可用于交互式应用或批处理。
3.Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
设计目的
其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
4.可扩展: Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
其它特性:
支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
5.语言内置模式匹配;闭包(closure);函数也可以看作一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
探索 Lua5.2 内部实现:编译系统(1) 概述
Lua 是一种轻量级、高效率的语言,其编译系统的实现至关重要。Lua 的编译过程需要将符合语法规则的chunk转换为可运行的closure,这一过程需要高效且巧妙的设计。closure对象是Lua运行时的函数实例,proto对象则代表了closure的原型,存储着函数的大部分信息,包括闭包与proto之间的关系,以及chunk与closure之间的对应关系。
编译系统的任务是将chunk转换为运行时可执行的closure。在这一过程中,需要理解chunk和closure的关系,以及chunk如何生成mainfunc proto,再为这个proto创建一个closure。每一个function statement都会生成一个对应的proto,并保存在外层函数的子函数列表中。所有最外层的function statement的proto会被保存到mainfunc proto的子函数列表中,形成以mainfunc为根节点的proto树。
编译系统被划分为三个模块:词法分析、语法分析和指令生成。Lua使用手写分析器进行词法和语法分析,以提高效率。词法分析将源代码拆分成token,供语法分析使用。语法分析采用“递归下降”的方法,生成最终的指令,构建proto树,即整个编译过程。
词法分析模块相对简单,主要任务是将源代码分解为token。Token包括类型和语义信息,用于后续的语法分析。Lua的全局状态信息由LexState结构体保存,它不仅包含词法分析状态,还包含了整个编译系统的全局状态。
语法分析和指令生成是整个编译过程的核心。语法分析器驱动整个编译过程,生成最终指令。分析过程中,词法分析器生成指令,直接用于构建proto树。编译过程中,使用FuncState结构体来保存函数的编译状态数据,这些数据会随着函数的压栈和弹栈进行保存和恢复。全局数据Dyndata用于保存每个FuncState对应的局部变量描述列表、goto列表和label列表。
编译系统的全局状态信息存储在LexState中,包含当前编译函数的FuncState和全局的Dyndata数据。FuncState通过f引用Proto,保存生成指令的列表。h引用一个table,用于生成常量表,当遇到常量时,查找表中是否存在该常量,以节省内存。编译过程会创建和销毁FuncState和BlockCnt,以管理函数和块的层次结构。
在整个语法分析过程中,Lua按照深度优先的顺序遍历FuncState树和BlockCnt树,只保存当前处理的编译状态,以减少内存使用。在分析过程中,Lua不构建完整的语法树对象,而是将过程中的语法结构保存在函数栈中,分析完成后立即丢弃。长跳转等异常处理机制用于处理错误,确保编译状态数据在出错时自动销毁。
在C stack中保存编译状态数据的原因与异常处理机制相关,使用longjump机制处理错误,确保所有当前的编译状态数据在出错时自动销毁。
程序开发中遇到的lua语言概念是什么呢?
Lua 教程
lua
Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua 是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于 年开发的,该小组成员有:Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo。
设计目的
其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua 特性
轻量级: 他用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
可扩展: Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
其它特性:
支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
语言内置模式匹配;闭包(closure);函数也可以看作一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
Lua 应用场景
游戏开发
独立应用脚本
Web 应用脚本
扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench
安全系统,如入侵检测系统
第一个 Lua 程序
接下来我们使用 Lua 来输出"Hello World!"
实例(Lua 5.3)
print("Hello World!")
《Lua5.4 源码剖析——基本数据类型 之 Function》
在编程语言中,函数作为重要的元素,可以分为第一类值语言和第二类值语言。第一类值语言如Lua,其函数与数值类型、布尔类型地位相同,可动态创建、存储与销毁;第二类值语言则无法实现这些操作。Lua是第一类值语言,支持动态函数创建与销毁。
在Lua中,函数的基本类型枚举为LUA_TFUNCTION,对应8位二进制为 。函数类型变体包括三种:LUA_VLCL(Lua闭包)、LUA_VLCF(C函数指针)和LUA_CCCL(C语言闭包)。闭包由函数与UpValue组成,UpValue为在当前函数外声明但函数内可以访问的变量,类似于局部变量但具备一定作用域。
闭包分为C类型闭包与Lua类型闭包。C类型闭包在Lua源代码中由C语言实现,主要用于调用C函数。Lua类型闭包则在Lua中动态创建,支持多层嵌套与UpValue管理。闭包实现方式包括C语言闭包和Lua闭包。
Lua闭包由ClosureHeader宏定义,包含闭包的类型标识、UpValue数组长度、垃圾回收列表等信息。闭包内部的函数通过Proto数据结构定义,包含参数数量、最大寄存器数量、UpValue数量等属性。Lua闭包中的UpValue通过UpVal类型管理,UpVal状态分为open和close两种,open状态时UpVal存储在链表中,close状态时UpVal的值被保存,直到函数返回时才被销毁。
在实现多返回值时,Lua通过调整运行堆栈的结构,将多个返回值合并,减少内存使用。在尾调用消除中,Lua在函数执行结束时,复用当前函数的栈空间进行下一次函数调用,避免了堆栈溢出的问题。Lua的尾调用优化使得函数调用效率更高,程序运行更稳定。