技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 源码分析 --> lua metatable使用和源码分析(二)

lua metatable使用和源码分析(二)

浏览:1265次  出处信息

    表是如何调度到元表的呢,上篇博文的例子我们看到当键值未能被查找到之后会调用__index事件对应的函数.现在举一个”__add”事件来遍历重要环节的源码.当然从luaV_execute开始.

    

lvm.c
=============
void luaV_execute (lua_State *L, int nexeccalls) {
    ....
    switch (GET_OPCODE(i)) {
        case OP_ADD: {
            arith_op(luai_numadd, TM_ADD);
            continue;
        }
....

    arith_op是个宏,会针对操作数进行类型判断,如果是数字类型就会立即进行相加,这就是为什么基本类型即便设置了加减等事件函数后还是没有效果,例如你为数字的加法设置了__add事件后数字相加不会掉用这个元表方法,后面会举一个例子.

     继续当发现操作数字为非数字类型后,就会调用Airth函数.

#define arith_op(op,tm) { \\
        TValue *rb = RKB(i); \\
        TValue *rc = RKC(i); \\
        if (ttisnumber(rb) && ttisnumber(rc)) { \\
          lua_Number nb = nvalue(rb), nc = nvalue(rc); \\
          setnvalue(ra, op(nb, nc)); \\
        } \\
        else \\
          Protect(Arith(L, ra, rb, rc, tm)); \\
      }

    Airth会对操作数再进行一次努力试图把其转化为数字,这样某些字符串也会被转换成数字相加.如果无法转化成数字则会尝试元表调用call_binTM函数.

static void Arith (lua_State *L, StkId ra, const TValue *rb,
                   const TValue *rc, TMS op) {
  TValue tempb, tempc;
  const TValue *b, *c;
  if ((b = luaV_tonumber(rb, &tempb)) != NULL &&
      (c = luaV_tonumber(rc, &tempc)) != NULL) {
    lua_Number nb = nvalue(b), nc = nvalue(c);
    switch (op) {
      case TM_ADD: setnvalue(ra, luai_numadd(nb, nc)); break;
      case TM_SUB: setnvalue(ra, luai_numsub(nb, nc)); break;
      case TM_MUL: setnvalue(ra, luai_nummul(nb, nc)); break;
      case TM_DIV: setnvalue(ra, luai_numdiv(nb, nc)); break;
      case TM_MOD: setnvalue(ra, luai_nummod(nb, nc)); break;
      case TM_POW: setnvalue(ra, luai_numpow(nb, nc)); break;
      case TM_UNM: setnvalue(ra, luai_numunm(nb)); break;
      default: lua_assert(0); break;
    }
  }
  else if (!call_binTM(L, rb, rc, ra, op))
    luaG_aritherror(L, rb, rc);
}

    call_binTM函数会对操作数进行尝试获取获取元表再根据事件名字(这里是__add)获取事件函数,如果获取成功则把操作数字压栈并调用这个事件函数.

static int call_binTM (lua_State *L, const TValue *p1, const TValue *p2,
                       StkId res, TMS event) {
  const TValue *tm = luaT_gettmbyobj(L, p1, event);  /* try first operand */
  if (ttisnil(tm))
    tm = luaT_gettmbyobj(L, p2, event);  /* try second operand */
  if (ttisnil(tm)) return 0;
  callTMres(L, res, tm, p1, p2);
  return 1;
}

    好了以上就是调度元表函数的过程.

    我们再来看看最常见的__index,__new_index函数是怎么被调用的,还是luaV_execute.

lvm.c
=====================

      case OP_GETTABLE: {
        Protect(luaV_gettable(L, RB(i), RKC(i), ra));
        continue;
      }
....
      case OP_SETTABLE: {
        Protect(luaV_settable(L, ra, RKB(i), RKC(i)));
        continue;
      }

    我们来看查找表,先进行常规的键值查找,如果找到则返回,如果键值未被发现则会查找TM_INDEX对应的元表事件函数.

void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) {
  int loop;
  for (loop = 0; loop < MAXTAGLOOP; loop++) {     const TValue *tm;     if (ttistable(t)) {  /* `t\' is a table? */       Table *h = hvalue(t);       const TValue *res = luaH_get(h, key); /* do a primitive get */       if (!ttisnil(res) ||  /* result is no nil? */           (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */
        setobj2s(L, val, res);
        return;
      }
      /* else will try the tag method, cant find so try metamethod */
    }
    else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX)))
      luaG_typeerror(L, t, "index");
    if (ttisfunction(tm)) {
      callTMres(L, val, tm, t, key);
      return;
    }
    t = tm;  /* else repeat with `tm\' */
  }
  luaG_runerror(L, "loop in gettable");
}

    luaT_gettmbyobj先查找到元表再查找对应的事件函数

const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event) {
  Table *mt;
  switch (ttype(o)) {
    case LUA_TTABLE:
      mt = hvalue(o)->metatable;
      break;
    case LUA_TUSERDATA:
      mt = uvalue(o)->metatable;
      break;
    default:
      mt = G(L)->mt[ttype(o)];
  }
  return (mt ? luaH_getstr(mt, G(L)->tmname[event]) : luaO_nilobject);
}

    回到luaV_gettable找到这个事件函数后就调用callTMres,参数压栈执行函数.

    这就是元表事件函数的调用过程,接下来会讲解元表的其它用法,未完待续.

建议继续学习:

  1. Nginx与Lua    (阅读:4691)
  2. Lua GC 的源码剖析 (2)    (阅读:3906)
  3. Ameba , 一个简单的 lua 多线程实现    (阅读:3594)
  4. Lua GC 的源码剖析 (4)    (阅读:3441)
  5. 使用 luajit 的 ffi 绑定 zeromq    (阅读:3363)
  6. Proto Buffers in Lua    (阅读:3150)
  7. Lua GC 的源码剖析 (1)    (阅读:3078)
  8. 一个 Lua 内存泄露检查工具    (阅读:3041)
  9. Lua GC 的源码剖析 (6) 完结    (阅读:2629)
  10. Lua GC 的源码剖析 (3)    (阅读:2345)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1