lua源码笔记-string

Posted 砸弹

tags:

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

结构

lua 中 string 分成两种 short string 和 long string,两者都使用 TString 结构存储数据,两者的 tag 都为LUA_TSTRING,而 variant 分别为1和2。

(lobject.h)

/* Variant tags for strings */
#define LUA_TSHRSTR (LUA_TSTRING | (1 << 4)) /* short strings */
#define LUA_TLNGSTR (LUA_TSTRING | (2 << 4))
/* long strings */

/*

** Header for string value; string bytes follow the end of this structure
** (aligned according to 'UTString'; see next).
*/

typedef struct TString {
CommonHeader;
lu_byte extra; /* reserved words for short strings; "has hash" for longs */
lu_byte shrlen; /* length for short strings */
unsigned int hash;
union {
size_t lnglen; /* length for long strings */
struct TString *hnext; /* linked list for hash table */
} u;
} TString;

其中 short string 保存在一个类型为 stringtable 的哈希表中,利用了结构中的 hnext 形成链表结构。而 long string 不需要这个指针,利用这部分存储长度 lnglen。short string 的长度则存储在 shrlen 中, shrlen是8位,因为 short string 保存40位以下的字符串,而 lnglen 是64位的。hash 部分用来存储字符串的hash值。而extra部分则用途特殊,留在后面说。

整个TString结构中没有存储字符串的内容,字符串的内容使用一个 char *指针紧跟在 TString结构后面。

(lobject.h)

/*
** Get the actual string (array of bytes) from a 'TString'.
** (Access to 'extra' ensures that value is really a 'TString'.)
*/

#define getstr(ts) \
check_exp(sizeof((ts)->extra), cast_charp((ts)) + sizeof(TString))

cast_charpts 转换为 char * 类型,加上 sizeof(TString) 将指针移到 TString 结构后面。

新建字符串

(lstring.c)

/*
** new string (with explicit length)
*/

TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
if (l <= LUAI_MAXSHORTLEN) /* short string? */
return internshrstr(L, str, l);
else {
TString *ts;
if (unlikely(l >= (MAX_SIZE - sizeof(TString))/sizeof(char)))
luaM_toobig(L);
ts = luaS_createlngstrobj(L, l);
memcpy(getstr(ts), str, l * sizeof(char));
return ts;
}
}

如果字符串长度小于等于 LUAI_MAXSHORTLEN(40)时,调用 intershrstr创建短字符串。

否则先检查长度是否超过上限。然后创建TString结构,最后将字符串复制到 TString结构后面。

(lstring.h)

#define sizelstring(l) (sizeof(TString) + ((l) + 1) * sizeof(char))

long string

(lstring.c)

/*
** creates a new string object
*/

static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) {
TString *ts;
GCObject *o;
size_t totalsize; /* total size of TString object */
totalsize = sizelstring(l);
o = luaC_newobj(L, tag, totalsize);
ts = gco2ts(o);
ts->hash = h;
ts->extra = 0;
getstr(ts)[l] = '\0'; /* ending 0 */
return ts;
}


TString *luaS_createlngstrobj (lua_State *L, size_t l) {
TString *ts = createstrobj(L, l, LUA_TLNGSTR, G(L)->seed);
ts->u.lnglen = l;
return ts;
}

这里可以看到 createstrobj 先计算了需要的内存,然后调用 luaC_newobj 分配内存并设置 tag 值,然后设置了 extra 为0,并设置了 TString 之后的字符串末尾为 '\0',而 luaS_createlngstrobj 则设置了 lnglen

需要特别注意到,这里将 hash 设置成了 G(L)->seed,这里是全局的随机种子,并不是这个字符串的 hash 值,因为 long string 并不需要立即使用它的 hash 值,所以只在需要hash值时才生出。

(lstring.c)

unsigned int luaS_hashlongstr (TString *ts) {
lua_assert(ts->tt == LUA_TLNGSTR);
if (ts->extra == 0) { /* no hash? */
ts->hash = luaS_hash(getstr(ts), ts->u.lnglen, ts->hash);
ts->extra = 1; /* now it has its hash */
}
return ts->hash;
}

luaS_hashlongstr 用来生成 long string 的哈希值,这里可以看到当 long stringextra 为 0 时,先使用字符串、长度和随机种子生出hash值,然后将 extra 置为 1 ,这样下次就不用再次生出 hash 值了。

short string

然后看看创建 short string 的逻辑,所有的short string 会保存在一个全局的stringtabhle中。

(lstate.h)

typedef struct stringtable {
TString **hash;
int nuse; /* number of elements */
int size;
} stringtable;

typedef struct global_State {
// ...
stringtable strt; /* hash table for strings */
// ...
} global_State;

stringtable 包括三个字段,hash 是一个 TString 链表的数组,nuse 表示其中字符串数量,size 为数组长度。

(lstring.c)

/*
** Checks whether short string exists and reuses it or creates a new one.
*/

static TString *internshrstr (lua_State *L, const char *str, size_t l) {
TString *ts;
global_State *g = G(L);
stringtable *tb = &g->strt;
unsigned int h = luaS_hash(str, l, g->seed);
TString **list = &tb->hash[lmod(h, tb->size)];
lua_assert(str != NULL); /* otherwise 'memcmp'/'memcpy' are undefined */
for (ts = *list; ts != NULL; ts = ts->u.hnext) {
if (l == ts->shrlen && (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) {
/* found! */
if (isdead(g, ts)) /* dead (but not collected yet)? */
changewhite(ts); /* resurrect it */
return ts;
}
}
/* else must create a new string */
if (tb->nuse >= tb->size) { /* need to grow string table? */
growstrtab(L, tb);
list = &tb->hash[lmod(h, tb->size)]; /* rehash with new size */
}
ts = createstrobj(L, l, LUA_TSHRSTR, h);
memcpy(getstr(ts), str, l * sizeof(char));
ts->shrlen = cast_byte(l);
ts->u.hnext = *list;
*list = ts;
tb->nuse++;
return ts;
}
  1. 先计算了字符串的哈希值 h

  2. 然后去全局的哈希表 global_State.strt 中查找,找到了直接返回

  3. 没有找到时,先根据需要调整哈希表的大小。

  4. 然后分配新字符串的内存空间,设置字符串内容和长度。

  5. 然后将字符串加入到全局哈希表中

  6. 最后调整全局哈希表中的对象数量

之前已经看过了 extra 字段在 long string 用来标记是否已经生成哈希值,而在 short string 则是用来标记关键字。

(llex.c)

/* ORDER RESERVED */
static const char *const luaX_tokens [] = {
"and", "break", "do", "else", "elseif",
"end", "false", "for", "function", "goto", "if",
"in", "local", "nil", "not", "or", "repeat",
"return", "then", "true", "until", "while",
"//", "..", "...", "==", ">=", "<=", "~=",
"<<", ">>", "::", "<eof>",
"<number>", "<integer>", "<name>", "<string>"
};

void luaX_init (lua_State *L) {
int i;
TString *e = luaS_newliteral(L, LUA_ENV); /* create env name */
luaC_fix(L, obj2gco(e)); /* never collect this name */
for (i=0; i<NUM_RESERVED; i++) {
TString *ts = luaS_new(L, luaX_tokens[i]);
luaC_fix(L, obj2gco(ts)); /* reserved words are never collected */
ts->extra = cast_byte(i+1); /* reserved word */
}
}

在初始化阶段,会把所有关键字字符串都建好,然后把 extra设为正整数。这样在语法分析时就能通过extra来判断一个字符串是否为关键字。

(lstring.h)

#define isreserved(s) ((s)->tt == LUA_TSHRSTR && (s)->extra > 0)

缓存

最后 lua 还为字符串做了一层缓存。

(lstring.c)

/*
** Create or reuse a zero-terminated string, first checking in the
** cache (using the string address as a key). The cache can contain
** only zero-terminated strings, so it is safe to use 'strcmp' to
** check hits.
*/

TString *luaS_new (lua_State *L, const char *str) {
unsigned int i = point2uint(str) % STRCACHE_N; /* hash */
int j;
TString **p = G(L)->strcache[i];
for (j = 0; j < STRCACHE_M; j++) {
if (strcmp(str, getstr(p[j])) == 0) /* hit? */
return p[j]; /* that is it */
}
/* normal route */
for (j = STRCACHE_M - 1; j > 0; j--)
p[j] = p[j - 1]; /* move out last element */
/* new element is first in the list */
p[0] = luaS_newlstr(L, str, strlen(str));
return p[0];
}

以上是关于lua源码笔记-string的主要内容,如果未能解决你的问题,请参考以下文章

lua 1.0 源码分析 -- 总结

lua源码分析之string类型的实现

lua入门笔记7 字符串库

Lua5.2笔记

Lua中string.format占位符的使用

Lua学习笔记-table