lua源码解析与笔记整理二,函数的解析和调用过程

这篇博客详细解析了Lua中函数的存储结构、参数存放、执行过程以及返回值传递。通过示例代码,逐步介绍了从输入代码到生成字节码及执行的过程,包括OP_CLOSURE、OP_CALL等指令的作用,以及OP_TAILCALL在函数嵌套调用中的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇记录了local变量的解析过程,今天整理下函数funciton在虚拟机的解析和调用过程;

主要目标:函数怎么存放,参数怎么存放,怎么执行函数和传递参数,返回值怎么传递。

来个简单的例子 ,在lua命令行输入如下代码

local myfun = function(a,b)
    return a + b
end 

一般来说虚拟机都是阻塞在读取输入的地方,当输入上面代码后,接下来看下函数在虚拟机里面会变成什么样子。

先介绍function在虚拟机的存储结构:FuncState 这里用fs表示,挑几个重要的讲下,这东西跟luaState概念类似(状态机???)

typedef struct FuncState{
    Proto * f ;   //类似c++代码段+函数栈,真正存储字节码和栈变量的地方
    Table*  h ;   //哈希表,用于快速查找常量所在数组(f->k[])对应的下标index
    int pc;       //指向当前需要执行的字节码,类似于指令寄存器 ip,每执行一条+1
    int freereg;  //当前空闲的栈序号,从0开始,每入栈一个常量变量等,freereg+1,出栈-1
    short nlocalvars;  //指示在f->locvars[]数组中有多少个元素(这个数组存局部变量)
    ......其他的用到再说

}FuncState;

Proto* f在这里也介绍下:

typedef struct Proto{
    TValue *k ;  //保存常量用的数组(向量)
    int sizek;
    Instruction *code;  //字节码真正保存处,fs->pc指向要执行的指令处
    struct LocVar *locvars;   //LocVar仅保存变量名和生命周期起始点和终点,用数组下标对应栈位置
    int sizelocars; //仅表示locvars的分配的空间,变量个数在fs->nlocvars中保存
    int numparams; //参数个数 function(a,b)中的a,b,会优先入栈占领1,2两个位置
    。。。。。//其他留待后续研究
}Proto;

1、解析准备过程略过,直接进入statement函数,local对应的是 TK_LOCAL(词法单元还是语法单元?不必深究,管他呢)

case TK_LOCAL:{
    luaX_next(ls); //解析local后面的一个元素,这里是myfun,对应TK_NAME
    if(testnext(ls,TK_FUNCTION)
        localfunc(ls)
    else
         localstat(ls);//局部变量,上一篇介绍过了
    
}

这里进入localstat(ls), 局部变量上一篇介绍过,这里略过,大致过程如下:用new_localvar,生成一个变量,并将freereg+1,表示占用一个栈位置,其实里面什么都没有,等到解析完(执行完?这里是模拟执行过程) "="后面的才是真正赋值。

static void localstat(LexState *ls)   //LexState今天研究重点不在这,大致看成输入源和解析缓存
{
    .......
    do
    {
        new_localvar(ls,str_checkname(ls),nvars++);
    }while(testnext(ls,",));  //local变量定义可一次定义多个,用“,”隔开,如local a,b,c = 1,2,3
    //注意完成上面过程后 fs -> f->locvars[0].name = "myfun", fs->nlocvars = 1 了
    //其实是TString类型的,这里直接用字符串"myfun"方便理解了
    if (testnext(ls,"="))  //赋值表达式
        nexps = explist1(ls,&e); //读取"="右边的数值列表,本次示例仅一个function(a,b)的定义
    else
    {
        ....
        //仅占位,不赋值,如 local a,b,c
    }

    adjuxt_assign(ls,nvars,nexps,&e); //占用栈空间,调整freereg的数值
    ....

}

2、接着深入explist1(ls, &e),看看function是怎解解析的

explist1(ls,e)
{
    .....
    expr(ls, e)
    {
        subexpr(ls, e)
        {
            ....
            simpleexp(ls, e);  //经过层层调用,做种进入这个函数
        }
    }


}

进过一系列的追踪后,发现进入了simpleexp(ls,e)函数,一般来说赋值,函数都是进入这个解析函数中

static void simpleexp(LexState* ls, expdesc * v)
{
    switch(ls->t.token)  //每个token都是解析到的一个单独的元素,如TK_NAME, TK_LOCAL等
    {
        .....
        case TK_FUNCTION:
        {
            luaX_next(ls);  //读取后面的“(”,返回
            body(ls, v, 0 , ls->linenumber);  //终于进入我们想要去的地方了,前面都是一直在函数外面打转转
        }
       .......
    }

}

3、正式进入函数解析 body

static void body(LexState* ls, expdesc *e, int needSelf, int line)
{
    FuncState fs;  //圈起来,要考, 这就是我们一开始介绍的函数保存的形式了
    open_func(ls, &fs);  //给fs->f指针new一个Proto,各种变量赋初值如,fs->freereg = 0,
                        //fs->pc = 0; fs->nlocvars = 0; fa->h = newTable等,最后将
                        //fs->h压入栈,将fs->f压入栈(解析完会不会出栈我们拭目以待)
                        //其中fs->f->souce保存了源代码文件名,可用于调试使用
    //注意,这里ls->fs已经是刚才圈起来的FuncState,不是一开始用的那个了
    //ls->fs是链表结构,链表头已经换新了
    checknext(ls,'(');  //左括号则读入下一个token(这里是a),否则报错
    parlist(ls);  //解析参数列表,直到
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值