LUA源码分析2---字符串

1,267 阅读5分钟

1. LUA字符串定义

本文主要源码分析基于Lua5.3.1.
之前已经做过Lua内部类型的分析。

LUA源码分析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;
    
}