1. LUA字符串定义
本文主要源码分析基于Lua5.3.1.
之前已经做过Lua内部类型的分析。
在上文中,已经分析了LUA的数据类型抽象。 本文就来分析下Lua语言是如何利用这些抽象的数据类型来保存字符串类型。
lua中关于字符串的操作都放到了如下两个文件中:
- lstring.h
- lstring.c
lua中对于字符串类型的定义放在lobject.h中。
1.1 TString定义
先看下TString定义:
/*
** 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;
从注释可以看出TString可以表示两种字符串:
- short string
- long string
1.2 创建TString对象
上篇文章中提到,对于GCObject
都会调用一个工厂函数:luaC_newobj (lua_State *L, int tt, size_t sz)
。
/*
** creates a new string object
*/
static TString *createstrobj (lua_State *L, const char *str, 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;
memcpy(getaddrstr(ts), str, l * sizeof(char));
getaddrstr(ts)[l] = '\0'; /* ending 0 */
return ts;
}
每次存放LUA字符串的变量,实际上存放的并不是一个真正的字符串数据, 而是一个字符串的引用。比如如下代码:
a = "str1"
b = "str1"
其实a和b引用的是同一份数据。
在lua内部有一个大的hash表,里面存放了所有了字符串。
如下图所示:
这个结构在LUA虚拟机中叫做
stringtable
。
定义如下:
typedef struct stringtable {
TString **hash;
int nuse; /* number of elements */
int size;
} stringtable;
这个结构保存在global_State
中。
下面看看LUA虚拟机已启动的时候,是怎么初始化的。
2. 虚拟机启动字符串表的初始化
在LUA程序开始执行一个LUA脚本的时候,其实都会调用一个函数f_luaopen
.在这个函数中会调用luaS_init(L);
这个函数,这个函数其实就是对上文提到的hashtable
进行初始化。
/*
** Initialize the string table and the string cache
*/
void luaS_init (lua_State *L) {
global_State *g = G(L);
int i;
luaS_resize(L, MINSTRTABSIZE); /* initial size of string table */
/* pre-create memory-error message */
g->memerrmsg = luaS_newliteral(L, MEMERRMSG);
luaC_fix(L, obj2gco(g->memerrmsg)); /* it should never be collected */
for (i = 0; i < STRCACHE_SIZE; i++) /* fill cache with valid strings */
g->strcache[i][0] = g->memerrmsg;
}
看到一上来,就调用了一个叫做luaS_resize
的函数,从这个函数的名字就能看出来,这个函数两种场景会调用:
- 场景1:初始调用(我们现在代码看到的)
- 场景2:当hash桶太小,而元素太多,导致数据寻找渐渐的退化为链表搜索的时候;(这个调用时机我们待会看)
场景1:这个函数其实就是按照MINSTRTABSIZE
的大小来初始化hash桶。
下面来看看这个函数怎么实现的:
/*
** resizes the string table
*/
void luaS_resize (lua_State *L, int newsize) {
int i;
stringtable *tb = &G(L)->strt;
if (newsize > tb->size) { /* grow table if needed */
luaM_reallocvector(L, tb->hash, tb->size, newsize, TString *); //这里还涉及到垃圾回收的内容,我们本次分析不考虑,暂时认为就是普通的realloc
for (i = tb->size; i < newsize; i++) //把后面申请那一块hash桶都初始化
tb->hash[i] = NULL;
}
for (i = 0; i < tb->size; i++) { /* rehash,把前面原有的hash桶进行重新分配 */
TString *p = tb->hash[i];
tb->hash[i] = NULL;
while (p) { /* for each node in the list */
TString *hnext = p->u.hnext; /* save next */
unsigned int h = lmod(p->hash, newsize); /* new position */
p->u.hnext = tb->hash[h]; /* chain it */
tb->hash[h] = p;
p = hnext;
}
}
if (newsize < tb->size) { /* shrink table if needed ,注释很清晰,如果是newsize小于原来大小,证明这是一个缩容操作*/
/* vanishing slice should be empty */
lua_assert(tb->hash[newsize] == NULL && tb->hash[tb->size - 1] == NULL);
luaM_reallocvector(L, tb->hash, tb->size, newsize, TString *);
}
tb->size = newsize;
}
3. 创建一个字符串
3.1 Lua中字符串的定义
在lua虚拟机里,字符串类型对象的数据结构做了封装和抽象,一般结构UTString
是这样的:
/*
** Ensures that address after this type is always fully aligned.
*/
typedef union UTString {
L_Umaxalign dummy; /* ensures maximum alignment for strings */
TString tsv;
} UTString;
而一个TString
的定义如下:
/*
** 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;
字符的内容都保存在TString
的后面。最终使用\0
结尾。
大致结构如下:
3.2 Lua中字符串的创建
在lua虚拟机里面创建一个字符串调用的函数是luaS_newlstr
/*
** 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
称为短字符串,否则称为长字符串。
对于短字符串来说,调用的是函数internshrstr
.看下实现:
/*
** 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);
unsigned int h = luaS_hash(str, l, g->seed); //先计算下当前这个str的hash值
TString **list = &g->strt.hash[lmod(h, g->strt.size)]; //找到对应的hash桶的入口
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;
}
}
//发现链表已经过长,触发resize操作
if (g->strt.nuse >= g->strt.size && g->strt.size <= MAX_INT/2) {
luaS_resize(L, g->strt.size * 2);
list = &g->strt.hash[lmod(h, g->strt.size)]; /* recompute with new size */
}
ts = createstrobj(L, str, l, LUA_TSHRSTR, h);
ts->shrlen = cast_byte(l);
ts->u.hnext = *list; //新创建的ts的尾指针指向hash桶口
*list = ts; //将
g->strt.nuse++;
return ts;
}
如果当前hash桶中没有找到这个字符串,就需要创建一个新的。
如果是长字符串,LUA的处理逻辑其实是和短字符串处理差不多的,只是两个Obj的tag不同而已。
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <string.h>
int main(void)
{
char buff[256] = {0};
int error;
lua_State *L = luaL_newstate();
luaopen_base(L);
luaopen_table(L);
luaopen_io(L);
luaopen_string(L);
luaopen_math(L);
const char *luaStr = "print(\"Hello World\")";
error = luaL_loadbuffer(L, luaStr, (size_t)strlen(luaStr), NULL) || lua_pcall(L,0,0,0);
if(error)
{
fprintf(stderr, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
// while(fgets(buff, sizeof(buff), stdin) != NULL)
// {
// error = luaL_loadbuffer(L, buff, (size_t)strlen(buff), NULL) || lua_pcall(L,0,0,0);
// if(error)
// {
// fprintf(stderr, "%s", lua_tostring(L, -1));
// lua_pop(L, 1);
// }
// }
lua_close(L);
return 0;
}