欢迎来到【kerman源码】【阅读源码的意思】【河南离温州源码】lua源码理解-皮皮网网站!!!

皮皮网

【kerman源码】【阅读源码的意思】【河南离温州源码】lua源码理解-皮皮网 扫描左侧二维码访问本站手机端

【kerman源码】【阅读源码的意思】【河南离温州源码】lua源码理解

2024-12-24 09:42:12 来源:{typename type="name"/} 分类:{typename type="name"/}

1.LuaJIT源码分析(二)数据类型
2.《Lua5.4 源码剖析——基本数据类型 之 数字类型》
3.Unlua源码解析(一) 通过 UE 命名空间访问C++类型
4.Unlua源码解析(附) 读源码的码理前置知识
5.Lua5.4 源码剖析——性能优化与原理分析
6.Unlua源码解析(附二) 源码中的重要类及核心函数逐行解释

lua源码理解

LuaJIT源码分析(二)数据类型

       LuaJIT,作为Lua的码理高性能版本,其源码分析中关于数据类型处理的码理细节颇值得研究。它在数据结构的码理定义上与Lua 5.1稍有不同,通过通用的码理数据结构TValue来表示各种Lua数据类型,但其复杂性体现在了内含的码理kerman源码若干宏上,增加了理解的码理难度。这些宏如LJ_ALIGN、码理LJ_GC、码理LJ_ENDIAN_LOHI、码理LJ_FR2等,码理分别用于内存对齐、码理GC模式的码理选择、大小端判断以及浮点数编码格式的码理选择。

       LJ_ALIGN宏用于确保struct内存对齐,码理以提高内存访问效率。LJ_GC宏在当前平台为位且无强制禁用的情况下生效,表明LuaJIT支持位GC(垃圾回收)模式。LJ_ENDIAN_LOHI宏则根据平台的字节顺序来确定结构的布局,而x平台采用小端序。

       对于TValue结构的定义,通过处理宏后可以简化为一个位的结构体,包含一个union,用于统一表示Lua的各种数据类型。这种设计利用了NaN Boxing技术,即通过在浮点数编码中预留空间来实现不同类型数据的紧凑存储。每个类型通过4位的itype指针来标识,使得数据的解析与存储变得高效。

       对于number数据类型,其值被存储在一个double中,而其他类型如nil、true、false等则利用剩余的空间来标识其类型。这种设计允许LuaJIT在内存中以一种紧凑且高效的方式存储各种数据类型,同时通过简单的位操作就能识别出具体的数据类型。

       对于GC对象(如string、table等),LuaJIT通过特定的itype值来区分它们与普通数据类型,以及与值类型(如nil和bool)和轻量级用户数据的差异。通过宏判断,LuaJIT能够快速识别出TValue是否为GC对象,以及具体是哪种类型的GC对象。

       在开启LJ_GC模式下,GC对象的地址被存储在TValue的特定字段gcr中,提供位的地址支持。虽然前位用于标识数据类型,但实际使用时仅利用了低位的地址空间,对于大多数实际应用而言,这部分内存已经绰绰有余。

       在GCobj数据结构中,通过union的特性实现不同类型对象的共通性与特定性。GChead提供了通用的接口来获取对象的通用信息,而nextgc、marked等字段用于实现垃圾回收机制。通过gct字段,LuaJIT能够将一个GCObj转换为实际的类型对象,进一步增强了内存管理的灵活性。

       对于整数类型,默认情况下LuaJIT使用double进行存储以确保精度,但在实际应用中,阅读源码的意思频繁使用的整数通过宏LJ_DUALNUM启用,以int类型存储,提高了数据处理的效率。此时,TValue的i字段用于保存int值,同时通过位移操作确保了数据的正确存储与解析。

《Lua5.4 源码剖析——基本数据类型 之 数字类型》

       数字类型在编程中分为整数和浮点数两种。在Lua语言的5.3版本之前,所有数字都被底层实现为浮点数,整数的概念并未独立出来,而是通过浮点数的IEEE表示法进行表示与数据存储。这样,在进行整数运算时,可能会在多次运算后累积产生出意外的浮点误差。因此,从Lua5.3版本开始,Lua引入了对整数的支持,使其不再依赖于浮点数进行表示,并且支持位运算等整数运算操作符。

       在Lua语言中,每个基础对象需要存储其类型标识,这个标识在源码《lua.h》中定义为tt,数字类型的tt枚举值为LUA_TNUMBER(对应数字3)。由于数字类型分为整型和浮点型,它们通过类型变体来区分。在源码《lobject.h》中,类型变体LUA_VNUMINT表示整型,而LUA_VNUMFLT表示浮点型。

       数字类型在TValue中定义了Value字段,这个字段包含i和n两个字段,用于分别存储整型和浮点型的数值。在历史原因的影响下,lua_Number并不是指所有数字类型,而是专门指浮点类型;lua_Integer则专门指整型。因此,设置整数或浮点数时,需要先设置Value字段中的n字段(整型)或i字段(浮点型),然后使用settt_宏设置type tag(tt)字段为对应值LUA_VNUMFLT或LUA_VNUMINT。

       在底层,数字类型的数据类型具体表现为lua_Integer和lua_Number。在源码《lua.h》中声明,lua_Number为LUA_NUMBER,lua_Integer为LUA_INTEGER。深入学习它们的定义,可以看到整型有int、long、long long三种类型,浮点型有float、double、long double三种类型。Lua5.4的默认配置中,整型使用long long类型,浮点型使用double类型。在Windows平台上,整型使用__int类型。

       至此,数字类型的讲解就告一段落。希望本文对理解Lua语言中的数字类型有所帮助。

Unlua源码解析(一) 通过 UE 命名空间访问C++类型

       通过UE4的河南离温州源码命名空间访问C++类型的机制,让我们从一个具体的例子出发,即UE4.UKismetSystemLibrary.PrintString(“hello”),来深入解析这一过程。在Unlua提供的例子中,HelloWorld的实现展现了Lua与C++的交互方式。要理解为什么Lua的代码能最终调用C++的方法,并且成功执行,我们需要从底层逻辑出发,解析这一过程中的关键步骤。

       首先,我们从Unlua.lua中的声明开始,UE4实际上被表示为全局表_G,其元表为global_mt,Index元方法为global_index。当我们在Lua代码中尝试访问UE4的成员,如UKismetSystemLibrary,实际上是在查找全局表_G中的“UKismetSystemLibrary”。为了实现这一查找,我们引入了元方法,即global_index方法,其在Lua代码中扮演了关键角色。

       在访问过程中,当Lua尝试获取表中不存在的“UKismetSystemLibrary”时,会触发元方法global_index。这个过程实际上涉及到一系列的函数调用,包括RegisterClass等。注册类的逻辑在于,将C函数注册为Lua端可以通过全局名称访问的函数。在这一过程中,UE4.UKismetSystemLibrary最终会成为一个Lua端的表,其元表指向自身,并且通过特定的元方法(如Class_Index)来处理访问与调用。

       在UE4.UKismetSystemLibrary.PrintString(“Hello”)的调用中,我们可以看到一系列的执行逻辑。首先,通过一系列函数调用,UE4.UKismetSystemLibrary表中实现了PrintString方法的描述信息与调用机制。这个过程涉及到类的注册、属性与方法的描述、以及在Lua端的表中存储这些描述信息。

       最终,当执行PrintString方法时,Lua端的调用实际转化为C++端的函数调用。这一过程涉及到参数的转换、方法的调用机制(如PreCall、ProcessEvent、PostCall等),以及最终的返回值转换与处理。这一系列的步骤确保了Lua端的代码能够与C++端的方法进行交互,实现功能的调用与执行。

       通过这一解析,我们可以清晰地看到,UE4与Unlua的结合是如何通过元方法、表操作以及函数注册机制,实现了Lua与C++之间的高效通信与调用,使得跨语言编程成为可能。这一机制不仅展示了语言间的交互灵活性,也体现了底层设计在实现复杂功能中的重要性。

Unlua源码解析(附) 读源码的苹果手机hs源码前置知识

       在解析Unlua源码时,需要熟悉Lua的基本API和交互机制。以下为关键API及功能解析:

       1. lua_getfield(L, k):获取指定表中由key k指向的值,压入栈顶。

       2. lua_gettop(L):返回栈顶元素的索引,即栈的大小。

       3. lua_rawget(L, -2):与lua_getfield类似,获取t[k]的值压入栈顶,但不调用元方法。

       4. lua_rawset(L, -4):设置t[k] = v,同样不通过元方法。

       5. lua_remove(L, -2):移除栈中index为-2的内容,之后所有元素下移。

       6. Lua与C++交互机制:调用开始时,Lua参数依次压入栈;调用结束时,C++返回值压入栈,同时返回值数量。

       在lua.h中,lua与C交互的API如下:

       1.1 lua_register:将C函数设置为全局名称的新值,允许Lua端调用。

       1.2 lua_gettop:返回栈顶元素的索引,用于获取栈大小。

       1.3 lua_pop:弹出栈中指定数量的值。

       1.4 lua_tolstring:将指定位置的值转换为C字符串,并返回字符串长度。

       1.5 lua_tostring:与lua_tolstring类似,但返回长度为NULL。

       1.6 lua_getfield:将表中key指向的值压入栈顶。

       1.7 luaL_getmetatable:获取指定表的元表并入栈。

       1.8 luaL_newmetatable:创建新元表并入栈,或重用已有。

       1.9 lua_getmetatable:获取指定索引处的表的元表。

       1. lua_pushstring:将字符串入栈,Lua会做拷贝。

       1. lua_settable:设置表中key对应的值。

       1. lua_rawset:与lua_settable类似,不调用元方法。

       1. lua_gettable:从表中获取key对应的值。

       1. lua_rawget:与lua_gettable类似,不调用元方法。

       1. lua_pushinteger:将数字入栈。

       1. lua_pushlightuserdata:将指针入栈。

       1. lua_pushcclosure:创建闭包入栈。

       1. lua_pushvalue:复制指定位置的值入栈。

       1. lua_setmetatable:设置表元表。

       1. lua_getglobal:获取全局变量并入栈。

       1. lua_setglobal:设置全局变量值。

       1. lua_pushnil:入栈nil值。

       1. lua_upvalueindex:获取闭包中的upvalue。

       1. lua_touserdata:返回完整 userdata 或 light userdata 指针。

       1. lua_newtable:创建空表并入栈。

       1. lua_createtable:预分配空间后创建空表。

       1. lua_next:用于遍历表元素。

       1. lua_tolstring:将指定位置的值转换为C字符串。

       1. lua_tostring:与lua_tolstring类似,但不返回长度。

       1. lua_newuserdata:分配内存并创建 userdata。

       1. lua_call:调用Lua函数。

       1. lua_pcall:与lua_call类似,用于调用Lua函数。c 操作websphere源码

       在Lua中,存在一些全局方法如rawset和rawget,用于直接写入或读取表元素而避免元方法的调用。

       综上所述,通过掌握这些API,开发者能有效利用Lua与C++的交互机制,实现复杂、高效的数据处理和逻辑交互。

Lua5.4 源码剖析——性能优化与原理分析

       本篇教程将引导您深入学习Lua在日常编程中如何通过优化写法来提升性能、降低内存消耗。在讲解每个优化案例时,将附上部分Lua虚拟机源代码实现,帮助您理解背后的原理。

       我们将对优化的评级进行标注:0星至3星,推荐评级越高,优化效果越明显。优化分为以下类别:CPU优化、内存优化、堆栈优化等。

       测试设备:个人MacBookPro,配置为4核2.2GHz i7处理器。使用Lua自带的os.clock()函数进行时间测量,以精确到毫秒级别。为了突出不同写法的性能差异,测试通常循环执行多次并累计总消耗。

       下面是推荐程度从高到低的优化方法:

       3星优化

       全类型通用CPU优化:高频访问的对象应先赋值给local变量。示例:用循环模拟高频访问,每次访问math.random函数创建随机数。推荐程度:极力推荐。

       String类型优化:使用table.concat函数拼接字符串。示例:循环拼接多个随机数到字符串。推荐程度:极力推荐。

       Table类型优化:Table构造时完成数据初始化。示例:创建初始值为1,2,3的Table。推荐程度:极力推荐。

       Function类型优化:使用尾调用避免堆栈溢出。示例:递归求和函数。推荐程度:极力推荐。

       Thread类型优化:复用协程以减少创建和销毁开销。示例:执行多个不同函数。推荐程度:极力推荐。

       2星优化

       Table类型优化:数据插入使用t[key]=value方式。示例:插入1到的数字。推荐程度:较为推荐。

       1星优化

       全类型通用优化:变量定义时同时赋值。示例:初始化整数变量。推荐程度:一般推荐。

       Nil类型优化:相邻赋值nil。示例:定义6个变量,其中3个为nil。推荐程度:一般推荐。

       Function类型优化:不返回多余的返回值。示例:外部请求第一个返回值。推荐程度:一般推荐。

       0星优化

       全类型通用优化:for循环终止条件无需提前计算缓存。示例:复杂函数计算循环终止条件。推荐程度:无效优化。

       Nil类型优化:初始化时显示赋值和隐式赋值效果相同。示例:定义一个nil变量。推荐程度:无效优化。

       总结:本文从源码层面深入分析了Lua优化策略。请根据推荐评级在日常开发中灵活应用。感谢阅读!

Unlua源码解析(附二) 源码中的重要类及核心函数逐行解释

       源码解析:重要类及核心函数逐行解释

       1. FClassDesc

       该类用于描述一个类,包含类名、类大小和继承关系等信息。

       2. FFunctionDesc

       对应UE中的UFunction,存储更详细信息,如参数、元数据,允许FFunctionDesc调用方法。

       3. FProporityDesc

       描述参数,并提供参数在Lua和C++间转换的辅助方法。

       4. FFieldDesc

       用于描述字段的类。

       5. FReflectionRegistry

       用于注册反射信息,借助UE反射接口加载类。

       6. FLuaContext

       全局类,负责绑定Lua对象和实现Lua与C++间的交互。

       7. LuaCore

       包含很多关键方法,如注册类、注册方法,是Unlua的核心类。

       8. UUnLuaManager

       集成绑定Lua与C++的多种方法。

       FReflectionRegistry内重要方法

       2.1 RegisterClass

       -: 通过UE反射接口尝试加载指定类。

       : 调用RegisterClass方法。

       2.2 RegisterClass

       -: 若无参数,返回。

       -: 获取并检查类的类型信息,仅当类型为Struct时继续。

       -: 若已注册,使用注册信息;否则注册新信息,返回。

       2.3 RegisterClassInternal

       存名称和Struct到FClassDesc字典,便于后续使用。

       -: 创建FClassDesc并记录相关信息。

       -: 遍历父类,记录父类名称和Struct。

       2.4 GetClassChain

       获取类的继承链,OutChain表示类及其父类。

       LuaCore内重要方法

       3.1 Global_RegisterClass

       读取类型信息,注册类。

       3.2 RegisterClass

       记录反射信息,创建元表,便于Lua与C++交互。

       3.3 RegisterClassInternal

       创建元表,设置元方法,记录全局表中。

       3.4 RegisterClassCore

       创建元表,设置元方法,记录元表信息。

       3.5 SetTableForClass

       将类元表放入全局表。

       3.6 Class_Index

       处理类索引方法。

       3.7 GetField

       获取字段或方法。

       3.8 GetFunctionList

       获取模块内所有方法。

       3.9 PushObjectCore

       创建并绑定Lua对象。

       3. NewLuaObject

       创建Lua表表示UObject。

       FLuaContext内重要方法

       4.1 FindExportedReflectedClass

       通过名称查找导出的反射类。

       4.2 NotifyUObjectCreated

       : 存储新创建的Object。

       : 尝试绑定Lua到Object。

       4.3 TryToBindLua

       绑定Lua模块到UObject。

       UUnLuaManager内重要方法

       5.1 Bind

       新UObject实例创建时,创建Lua对象并绑定。

       5.2 BindInternal

       实现Lua绑定UObject的关键函数。

       方法涵盖模块名与C++对象关联、覆盖C++函数、处理动画覆盖等。

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 虚拟机数据与逻辑的流向。

Lua5.4 源码剖析——杂谈 之 如何调试Lua源码

       我们有时候写了一段Lua代码,希望能通过断点调试的方式看一下我们的代码在执行过程中Lua虚拟机的状态与运行流程。本篇教程我将教大家Windows与Mac环境下如何配置Lua源码调试环境。

       Lua调试环境需要有Lua源码,我们从官网下载源码:

       Windows下Lua源码调试环境搭建

       1)下载Visual Studio,可自行在官网下载最新版本即可:

       2)打开VIsual Studio,创建一个新的C++控制台工程,我这里以Visual Studio 版本进行举例:

       项目可任意命名,本例中我们命名为TestLua:

       3)工程中添加Lua源码文件:

       3.1)拷贝源代码文件到项目的文件夹,Makefile文件可以不拷贝:

       3.2)把上面这些文件导入工程:

       "

       .h

       头文件导入:导入所有".h"后缀文件到头文件文件夹中(右键头文件->添加->现有项):

       "

       .c

       源文件导入:导入所有".c"后缀文件到源文件的文件夹(右键源文件->添加->现有项):

       4)生成exe可执行文件:

       文件都导入完成了,这时候如果按"生成"或者"F5",会有如下的报错:

       这是因为除了我们创建项目工程的时候自带源文件中的一个main函数以外,Lua源码中也定义了两个Main函数。他们分别对应的是luac编译工具的启动函数和lua运行工具的启动函数。要想编译通过,我们只需要根据自己要调试目的,从3个main里面把用不到的2个main删掉或者重命名即可。

       本例中,我打算在自己的main里面实现通过dofile函数执行一个Lua文件的功能,所以我不需要启动lua和luac指令控制台,所以我把他们的main函数改名:

       luac.c:把main函数改名为luac_main函数:

       lua.c:把main函数改为lua_main:

       上述源码中多余的2个main函数都改名了,这时候已经能编译通过并生成出exe可执行文件了。

       接下来我们可以开始编写自己的main函数逻辑了,打开TestLua.cpp,输入以下内容,作用是运行一个在项目目录下名字为"testlua.lua"的lua文件:

       5)testlua.lua文件创建与编写:

       上述代码在运行时会执行testlua.lua文件,接下来我们就需要在工程目录下创建这个将要被执行的testlua.lua文件:

       打开testlua.lua文件,添加任意lua代码,这里我们简单调用print打印一句信息:

       6)在Visual Studio中按“F5”开启调试,可以看到控制台被成功运行,我们的lua文件也被成功执行,打印出了信息:

       7)断点调试指令OpCode:

       学习过我的《Lua源码剖析 之 虚拟机》系列教程的同学应该知道Lua的指令就是各种OpCode的执行,我们可以在《lvm.c》的下面这个地方加断点再按F5重新启动程序,程序在每执行一条OpCode指令就会在这处代码断点下来,这时候我们就能看到下一条要执行的OpCode是哪一条了:

       在本例中的print函数最终会执行到OP_CALL这个调用分支:

       Windows环境下搭建Lua源码调试环境的教程到此结束。

       Mac下Lua源码调试环境搭建

       因为大部分流程与上面Windows一样,所以我下面会省略一些重复步骤。

       1)下载XCode,可自行在AppStore进行下载。

       2)打开XCode,创建一个新的C++控制台工程,本例中命名为TestLua:

       3)工程中添加Lua源码文件:

       3.1)拷贝源代码文件到项目的文件夹,Makefile文件可以不拷贝:

       3.2)把拷贝后的文件导入工程:

       不需要区分".h"和".cpp",全选导进来就好了:

       4)与Windows流程同样,把源码自带的2个main函数改名:

       luac.c:把main函数改名为luac_main函数:

       lua.c:把main函数改为lua_main:

       把源码中多余的2个main函数都改名了,接下来同样,开始编写我们的main.cpp,打开该文件并添加代码如下代码。为了在mac下文件读取代码更简洁,在下面的Lua文件我暂时先使用文件的绝对路径,暂时把testlua.lua文件放在我的mac的桌面上进行读取:

       5)在mac的桌面上创建testlua.lua文件,添加任意lua代码:

       6)同理可正常运行或者加断点进行调试,这里不再赘述:

       总结

       本文我们学习了如何在Windows与Mac下搭建Lua源码调试环境。另外,我们上述使用的例子是通过dofile运行一个lua文件,同学们也可以试试保留lua.c里面的main函数,删掉另外两个,此时按开始调试可启动lua的即时解析控制台,在控制台里面可自行输入任意Lua代码,并可断点查看即时运行状态或最终结果,感兴趣的同学可以自行试试。

       不过,尽管能调试Lua源码,但如果之前没有学习过我的那些Lua源码剖析教程,可能很多地方会看不懂,所以这里建议有空的同学还是可以先去学习一下的。

       谢谢阅读。

《Lua5.4 源码剖析——基本数据类型 之 布尔类型》

       《Lua5.4 源码剖析——基本数据类型 之 布尔类型》

       Lua的基本数据类型中,布尔类型是最简单的一种。在Lua中,尽管通常认为布尔类型只有true和false两种值,但实际上,其在源码中的实现更为精细。Lua使用了TValue这个数据结构来存储所有类型,包括布尔类型。TValue包含了一个lu_byte类型的tt_(类型标记)和Value类型的value_(存储实际数据)。

       tt_字段占用1个字节,其中4个位用于存储基本类型(0-8代表nil到thread),2个位用于表示类型变体,1个位用于垃圾回收标志。布尔类型通过类型变体实现,它被声明为LUA_TBOOLEAN,当tt_的第5位为0时代表false,为1时代表true。

       判断布尔变量的宏定义在《lobject.h》中,而布尔类型的实际值并不存储在value_,而是直接在tt_字段中,以节省内存和判断复杂度。理解了这一点,我们就可以深入理解Lua中布尔类型的内存结构和使用方式。继续关注后续章节,将探讨其他基本数据类型在Lua5.4源码中的实现细节。