lua源码阅读:table长度问题

Posted gpSzn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了lua源码阅读:table长度问题相关的知识,希望对你有一定的参考价值。

lua源码版本:5.4.2


前言:

        table作为lua中一个非常重要且常用的数据类型.我们常常将它作为数组或k-v使用.本篇文章我们通过阅读源码来了解一下关于'#'长度操作符的取值逻辑.


数据结构:

        在阅读源码跟踪逻辑前先提两个重要的数据结构:

/* lobject.h *//* Table 定义 */typedef struct Table { CommonHeader; lu_byte flags; /* 1<<p means tagmethod(p) is not present */ lu_byte lsizenode; /* log2 of size of 'node' array */ unsigned int alimit; /* "limit" of 'array' array */ TValue *array; /* array part */ Node *node; Node *lastfree; /* any free position is before this position */ struct Table *metatable; GCObject *gclist;} Table;
/* Table hash部分的数据定义*/typedef union Node { struct NodeKey { TValuefields; /* fields for value */ lu_byte key_tt; /* key type */ int next; /* for chaining */ Value key_val; /* key value */ } u; TValue i_val; /* direct access to node's value as a proper 'TValue' */} Node;

本篇我们研读关于Table长度的问题,不过多关注其他数据主要关注以下3点:

    a.数组array部分存储值类型为TValue

    b.hash node部分值类型为Node

    c.alimit 它是衡量array长度限制值,非常关键后面代码会继续解读

这里插入一段关于Table的源码,看看node部分到底是什么

/* ltable.c */Table *luaH_new (lua_State *L) { GCObject *o = luaC_newobj(L, LUA_VTABLE, sizeof(Table)); Table *t = gco2t(o); t->metatable = NULL; t->flags = cast_byte(maskflags); /* table has no metamethod fields */ t->array = NULL; t->alimit = 0; setnodevector(L, t, 0); return t;}
/*** Creates an array for the hash part of a table with the given** size, or reuses the dummy node if size is zero.** The computation for size overflow is in two steps: the first** comparison ensures that the shift in the second one does not** overflow.*/static void setnodevector (lua_State *L, Table *t, unsigned int size) { if (size == 0) { /* no elements to hash part? */ t->node = cast(Node *, dummynode); /* use common 'dummynode' */ t->lsizenode = 0; t->lastfree = NULL; /* signal that it is using dummy node */ } else { int i; int lsize = luaO_ceillog2(size); if (lsize > MAXHBITS || (1u << lsize) > MAXHSIZE) luaG_runerror(L, "table overflow"); size = twoto(lsize); t->node = luaM_newvector(L, size, Node); for (i = 0; i < (int)size; i++) { Node *n = gnode(t, i); gnext(n) = 0; setnilkey(n); setempty(gval(n)); } t->lsizenode = cast_byte(lsize); t->lastfree = gnode(t, size); /* all positions are free */ }}

跟踪源码可知,其实hash部分是基于数组结构实现的一个散列表


有了Table结构的基本认识,下面我们就根据代码逻辑来一步步还原#的操作执行流程.(代码量较多下面的内容仅截取部分关键源码显示)


#的执行逻辑

        lua中长度操作对应OP_LEN操作指令

/* lvm.c */vmcase(OP_LEN) { Protect(luaV_objlen(L, ra, vRB(i))); vmbreak;}
.....
Table对应的长度操作函数为luaH_getn/*** Try to find a boundary in table 't'. (A 'boundary' is an integer index** such that t[i] is present and t[i+1] is absent, or 0 if t[1] is absent** and 'maxinteger' if t[maxinteger] is present.)** (In the next explanation, we use Lua indices, that is, with base 1.** The code itself uses base 0 when indexing the array part of the table.)** The code starts with 'limit = t->alimit', a position in the array** part that may be a boundary.**** (1) If 't[limit]' is empty, there must be a boundary before it.** As a common case (e.g., after 't[#t]=nil'), check whether 'limit-1'** is present. If so, it is a boundary. Otherwise, do a binary search** between 0 and limit to find a boundary. In both cases, try to** use this boundary as the new 'alimit', as a hint for the next call.**** (2) If 't[limit]' is not empty and the array has more elements** after 'limit', try to find a boundary there. Again, try first** the special case (which should be quite frequent) where 'limit+1'** is empty, so that 'limit' is a boundary. Otherwise, check the** last element of the array part. If it is empty, there must be a** boundary between the old limit (present) and the last element** (absent), which is found with a binary search. (This boundary always** can be a new limit.)**** (3) The last case is when there are no elements in the array part** (limit == 0) or its last element (the new limit) is present.** In this case, must check the hash part. If there is no hash part** or 'limit+1' is absent, 'limit' is a boundary. Otherwise, call** 'hash_search' to find a boundary in the hash part of the table.** (In those cases, the boundary is not inside the array part, and** therefore cannot be used as a new limit.)*/lua_Unsigned luaH_getn (Table *t) { unsigned int limit = t->alimit; if (limit > 0 && isempty(&t->array[limit - 1])) { /* (1)? */ /* there must be a boundary before 'limit' */ if (limit >= 2 && !isempty(&t->array[limit - 2])) { /* 'limit - 1' is a boundary; can it be a new limit? */ if (ispow2realasize(t) && !ispow2(limit - 1)) { t->alimit = limit - 1; setnorealasize(t); /* now 'alimit' is not the real size */ } return limit - 1; } else { /* must search for a boundary in [0, limit] */ unsigned int boundary = binsearch(t->array, 0, limit); /* can this boundary represent the real size of the array? */ if (ispow2realasize(t) && boundary > luaH_realasize(t) / 2) { t->alimit = boundary; /* use it as the new limit */ setnorealasize(t); } return boundary; } } /* 'limit' is zero or present in table */ if (!limitequalsasize(t)) { /* (2)? */ /* 'limit' > 0 and array has more elements after 'limit' */ if (isempty(&t->array[limit])) /* 'limit + 1' is empty? */ return limit; /* this is the boundary */ /* else, try last element in the array */ limit = luaH_realasize(t); if (isempty(&t->array[limit - 1])) { /* empty? */ /* there must be a boundary in the array after old limit, and it must be a valid new limit */ unsigned int boundary = binsearch(t->array, t->alimit, limit); t->alimit = boundary; return boundary; } /* else, new limit is present in the table; check the hash part */ } /* (3) 'limit' is the last element and either is zero or present in table */ lua_assert(limit == luaH_realasize(t) && (limit == 0 || !isempty(&t->array[limit - 1]))); if (isdummy(t) || isempty(luaH_getint(t, cast(lua_Integer, limit + 1)))) return limit; /* 'limit + 1' is absent */ else /* 'limit + 1' is also present */ return hash_search(t, limit);}

lua_getn即为Table长度的核心函数,代码中对于长度返回值大体分三种情况:

    a.如果limit大于0且Table的array[limit - 1]为空那么limit之前必有一个边界值即函数返回的长度值

         a.1 limit大于等于2且array[limit - 2]不为空则返回的长度为limit-1

         a.2 否则边界值就是通过二分查找法在0-limit直接查找边界值并返回

         

    b.不为a的情况下Table的尺寸为0或者有对应值

          b.1 array[limit]为空则返回limit

          b.2 否则就重新计算尺寸并判断array[limit - 1]是否为空如果为空则使用二分查找法在 初始alimit与新计算尺寸limit直接查找并返回该边界值

          

    c.以上情况都不满足的情况下

          c.1 如果table为空或者limit+1在table中不存在对应值则返回limit

          c.2 否则就将在 hash部分进行二分查找获取对应边界值作为#操作返回值


这里附上其中比较关键的代码片段:

/* ltable.c *//*** Returns the real size of the 'array' array*/LUAI_FUNC unsigned int luaH_realasize (const Table *t) { if (limitequalsasize(t)) return t->alimit; /* this is the size */ else { unsigned int size = t->alimit; /* compute the smallest power of 2 not smaller than 'n' */ size |= (size >> 1); size |= (size >> 2); size |= (size >> 4); size |= (size >> 8); size |= (size >> 16);#if (UINT_MAX >> 30) > 3 size |= (size >> 32); /* unsigned int has more than 32 bits */#endif size++; lua_assert(ispow2(size) && size/2 < t->alimit && t->alimit < size); return size; }}
static lua_Unsigned hash_search (Table *t, lua_Unsigned j) { lua_Unsigned i; if (j == 0) j++; /* the caller ensures 'j + 1' is present */ do { i = j; /* 'i' is a present index */ if (j <= l_castS2U(LUA_MAXINTEGER) / 2) j *= 2; else { j = LUA_MAXINTEGER; if (isempty(luaH_getint(t, j))) /* t[j] not present? */ break; /* 'j' now is an absent index */ else /* weird case */ return j; /* well, max integer is a boundary... */ } } while (!isempty(luaH_getint(t, j))); /* repeat until an absent t[j] */ /* i < j && t[i] present && t[j] absent */ while (j - i > 1u) { /* do a binary search between them */ lua_Unsigned m = (i + j) / 2; if (isempty(luaH_getint(t, m))) j = m; else i = m; } return i;}


以上便是#长度操作符的源码执行逻辑.如果我们想要知道一个已知table的#长度值就需要去跟踪查看关键字段alimit的值是如何变化的!下一篇文章将通过跟踪table的声明定义来了解alimit值是如何变化的.

以上是关于lua源码阅读:table长度问题的主要内容,如果未能解决你的问题,请参考以下文章

Lua5.4源码阅读—字符串

Lua table 的长度问题

lua 1.0 源码分析 -- 总结

Lua源码阅读推荐顺序

lua 源码阅读顺序

Table — 白话Lua系列零基础教程 第七期