快速掌握Lua 5.3 —— userdata (1)

Q:什么是”userdata”?

A:”userdata”分为两类,”full userdata”和”light userdata”。Lua使用他们来表示C中一些特殊的类型。前面的章节中,我们看到了如何通过C编写新的函数来扩展Lua;使用”userdata”,我们将可以通过C编写新的类新来扩展Lua。

Q:两种”userdata”的区别?

A:

\ “full userdata” “light userdata”
本质 一段在被创建时可以指定大小的内存区域,通常用来表示C中的结构体。 一小段固定的内存区域,通常用来表示C中的指针(void *)
使用 需要显式的创建一块儿内存,该段内存由Lua的垃圾回收器管理,使用者无需关心。 无需创建内存,它就相当于一个值(就像Lua中的数值一样),它所使用的内存空间不由Lua的垃圾回收器管理,所以使用者需要关心其内存使用。
创建 void *lua_newuserdata(lua_State *L, size_t size); void lua_pushlightuserdata(lua_State *L, void *p);
其他 可以指定其”metatable”和”metamethods”。 不能指定其”metatable”和”metamethods”。

Q:如何使用”full userdata”?

A:我们将使用数组来举例,因为其不涉及复杂的算法。

typedef struct NumArray
{
    int size;
    double values[1];    // 可变部分。
} NumArray;

数组声明为一个长度只是为了占位,因为C中不允许声明长度为0的数组,在实际程序中,我们会根据指定的大小来申请合适的空间,

// "n"为指定的大小。因为"NumArray"中已包含了一个元素的大小,所以需要减去。
sizeof(NumArray) + (n - 1) * sizeof(double)

首先来看几个在程序中会用到的函数,

/* 分配一块大小为"size"的内存空间作为"full userdata"使用,
 * 之后将内存空间的地址入栈,函数返回此地址。
 */
void *lua_newuserdata(lua_State *L, size_t size);

/* 检查"cond"是否为"true",如果为"false"则报错,并返回形如如下格式的错误,
 * "bad argument #arg to ‘funcname‘ (extramsg)"
 */
void luaL_argcheck (lua_State *L, int cond, int arg, const char *extramsg);

“mylib.c”文件中:

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

typedef struct NumArray
{
    int size;
    double values[1];
} NumArray;

static int newarray(lua_State *L)
{
    // 检查待创建数组大小参数是否为整数。
    int n = luaL_checkinteger(L, 1);
    size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
    NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);
    a->size = n;    // 设置数组的大小。
    return 1;    // 函数返回创建的"userdata"。
}

static int setarray(lua_State *L)
{
    // 获取传递的数组("userdata")的地址。
    NumArray *a = (NumArray *)lua_touserdata(L, 1);
    // 检查传递的"key"是否为整数。
    int index = luaL_checkinteger(L, 2);
    // 检查传递的"value"是否为数值。
    double value = luaL_checknumber(L, 3);

    luaL_argcheck(L, a != NULL, 1, "‘array‘ expected");
    luaL_argcheck(L, 1 <= index && index <= a->size, 2,
                  "index out of range");

    a->values[index - 1] = value;    // 设置数组的值。

    return 0;
}

static int getarray(lua_State *L)
{
    NumArray *a = (NumArray *)lua_touserdata(L, 1);
    int index = luaL_checkinteger(L, 2);

    luaL_argcheck(L, a != NULL, 1, "‘array‘ expected");
    luaL_argcheck(L, 1 <= index && index <= a->size, 2,
                  "index out of range");

    // 获取数组中指定的值并入栈。
    lua_pushnumber(L, a->values[index - 1]);

    return 1;    // 函数返回获取的值。
}

static int getsize(lua_State *L)
{
    NumArray *a = (NumArray *)lua_touserdata(L, 1);
    luaL_argcheck(L, a != NULL, 1, "‘array‘ expected");
    lua_pushnumber(L, a->size);    // 获取数组的大小。

    return 1;    // 函数返回数组的大小。
}

static const struct luaL_Reg arraylib[] = {
    {"new", newarray},
    {"set", setarray},
    {"get", getarray},
    {"size", getsize},
    {NULL, NULL}
};

extern int luaopen_mylib(lua_State* L)
{
    luaL_newlib(L, arraylib);

    return 1;
}

将”mylib.c”编译为动态连接库,

prompt> gcc mylib.c -fPIC -shared -o mylib.so -Wall
prompt> ls
mylib.c    mylib.so    a.lua

“a.lua”文件中:

local array = require "mylib"

a = array.new(1000)
print(a)    --> userdata: 0x8064d48
print(array.size(a))    --> 1000
for i = 1, 1000 do
    array.set(io.stdin, i, 1/i)
end
print(array.get(a, 10))    --> 0.1

Q:如何使用”userdata”的”metatables”?

A:上面例子的实现方式存在一个很大的安全漏洞。假如使用者编写了类似于如下的代码,array.set(io.stdin, 1, 0),将导致段错误(如果你足够幸运的话,可能会得到一个数组访问越界的错误)。因为array.set的第一个参数接收一个”userdata”,而io.stdin也是一个”userdata”,所以在参数检查的过程中不会报错,然而io.stdin却并不是我们所创建的数组。

为了解决这个问题,我们需要为我们所创建的数组增加一个特殊的标志,用来与其他的”userdata”加以区分。因为Lua代码不能更改”userdata”的”metatable”,所以我们使用一个全局唯一的”metatable”来作为这个特殊的标志。

还是先来看几个在程序中会用到的函数,

/* 在"registry"中创建一个供"userdata"使用的索引为"tname"的"metatable",
 * 并将此"metatable"的"__name"域设置为"tname"
 * (有些错误处理函数会使用"__name"域)。"metatable"创建成功,函数返回1。
 * 如果"registry"中已存在索引为"tname"的元素,则函数返回0。
 * 以上两种情况,函数均会将"tname"所对应的值入栈。
 */
int luaL_newmetatable(lua_State *L, const char *tname);

/* 将"registry"中索引为"tname"的"metatable"入栈。
 * 如果"registry"中没有索引为"tname"的元素,
 * 或是索引"tname"所对应的元素不是"metatable",那么函数将"nil"入栈。
 * 函数返回入栈值的类型。
 */
int luaL_getmetatable(lua_State *L, const char *tname);

// 从虚拟栈中弹出一个"table"作为索引"index"处值的新的"metatable"。
void lua_setmetatable(lua_State *L, int index);

/* 检查虚拟栈中索引"arg"处的值是否为一个"userdata",
 * 并且此"userdata"具有一个名为"tname"的"metatable"。
 * 如果是,则返回此"userdata"的地址,否则返回"NULL"。
 */
void *luaL_checkudata (lua_State *L, int arg, const char *tname);

“mylib.c”文件中:

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

typedef struct NumArray
{
    int size;
    double values[1];
} NumArray;

// 检查传递的数组是否合法,并返回数组的地址。
static NumArray *checkarray(lua_State *L)
{
    void *ud = luaL_checkudata(L, 1, "LuaBook.array");
    luaL_argcheck(L, ud != NULL, 1, "‘array‘ expected");

    return (NumArray *)ud;
}

// 检查传递的参数是否合法,并返回数组中指定元素的地址。
static double *getelem(lua_State *L)
{
    NumArray *a = checkarray(L);
    int index = luaL_checkinteger(L, 2);

    luaL_argcheck(L, 1 <= index && index <= a->size, 2,
                  "index out of range");

    return &a->values[index - 1];
}

static int newarray(lua_State *L)
{
    int n = luaL_checkinteger(L, 1);
    size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
    NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);

    luaL_getmetatable(L, "LuaBook.array");
    // 注意,这里使用的不是"luaL_setmetatable",两个函数的功能不同。
    lua_setmetatable(L, -2);

    a->size = n;

    return 1;
}

static int setarray(lua_State *L)
{
    double value = luaL_checknumber(L, 3);
    *getelem(L) = value;

    return 0;
}

static int getarray(lua_State *L)
{
    lua_pushnumber(L, *getelem(L));

    return 1;
}

static int getsize(lua_State *L)
{
    NumArray *a = checkarray(L);
    lua_pushnumber(L, a->size);

    return 1;
}

static const struct luaL_Reg arraylib[] = {
    {"new", newarray},
    {"set", setarray},
    {"get", getarray},
    {"size", getsize},
    {NULL, NULL}
};

extern int luaopen_mylib(lua_State* L)
{
    luaL_newmetatable(L, "LuaBook.array");
    luaL_newlib(L, arraylib);

    return 1;
}

“a.lua”中的代码保持不变,执行后会得到相同的结果。但是此时,如果你再向array.set传入io.stdin或是其他非法的”userdata”,将会得到明确的报错,

userdata: 0x1c960a8
1000.0
lua: a.lua:8: bad argument #1 to ‘set‘ (LuaBook.array expected, got FILE*)
stack traceback:
    [C]: in function ‘mylib.set‘
    a.lua:8: in main chunk
    [C]: in ?

附加:

1、尽管”full userdata”和”light userdata”在名字上给人感觉他们在占用资源上有很大差异。但实际上,”full userdata”也并不“昂贵”。

2、一般情况下,Lua中并不需要外部的数组,因为哈希表的一部分功能很好的实现了数组。但是对于非常大的数组而言,哈希表可能导致内存的大量浪费。

哈系表可以使用整数索引、字符串索引、”table”索引等等。但是数组只需要整数索引,而哈系表依旧分配了额外的内存,用于提供所有的功能。

在C中使用原生的数组,将比哈希表的实现方式节省50%的内存空间。

时间: 2024-08-23 11:22:09

快速掌握Lua 5.3 —— userdata (1)的相关文章

快速掌握Lua 5.3 —— userdata (2)

Q:如何使用"userdata"的"metamethods"? A:我们继续来修改上一节中的例子,这次我们的目标是使用面向对象的方式调用"userdata"的方法.这个目标既可以在Lua中实现,也可以在C库中实现,我们先来看一个比较简单的方式,在Lua中实现."mylib.c"中代码无需更改,只需要修改"a.lua"中的代码, local array = require "mylib"

快速掌握Lua 5.3 —— 资源管理

Q:Lua的"finalizer"? A:在我们之前看到的使用"userdata"的例子中,我们只关心如何创建并使用"userdata",从未关心何时以及如何释放我们创建的"userdata",因为这些事都由Lua的垃圾回收器帮我们处理.然而很多时候,程序并不会这么简单,有可能在其中还会涉及到文件句柄,窗口句柄等,此时这些资源就需要创建者进行管理. 一些面向对象语言提供了析够器用来帮助用户管理这些资源,Lua同样提供了类似的机

快速掌握Lua 5.3 —— 字符串库 (3)

Q:什么情况下"pattern"会匹配空串? A:要小心的使用*和-,因为它们可以匹配零次. -- 如果你打算用"%a*"匹配单词,你会发现到处都是单词. print(string.find(";$% **#$hello13", "%a*")) --> 1 0 print(string.find(";$% **#$hello13", "%a*", 6)) --> 6 5 --

快速掌握Lua 5.3 —— 编写提供给Lua使用的C库函数的技巧 (2)

Q:什么是"registry"? A:有时候,我们需要在程序中使用一些非局部的变量.在C中我们可以使用全局变量或是静态变量来实现,而在为Lua编写C库的过程中,使用以上类型的变量并不是一个好的方式.首先,这些变量中无法存储Lua的值.其次,这些变量如果在多个Lua状态机中被使用,则很可能造成非预期的结果. 一个替代方案是,将这些值存储在Lua的全局变量中.这种方式解决了上面提到的两个问题,Lua全局变量可以存储任何Lua的值,同时每一个Lua状态机都有自己独立的一套全局变量.但这依旧不

Lua中的userdata

[话从这里说起] 在我发表<Lua中的类型与值>这篇文章时,就有读者给我留言了,说:你应该好好总结一下Lua中的function和userdata类型.现在是时候总结了.对于function,我在<Lua中的函数>这篇文章中进行了总结,而这篇文章将会对Lua中的userdata进行仔细的总结.对于文章,大家如果有任何疑议,都可以在文章的下方给我留言,也可以关注我的新浪微博与我互动.学习,就要分享,我期待你的加入. [userdata是啥?] userdata是啥?简单直译就是用户数

lua笔记之userdata

1.一直使用框架里封装好的c库,想着自己一点一点的写些例子,学习下,以后需要c库,可以自己写了. 下边是一个简单的userdata的例子--数组操作. newarray.c #include "lua.h" #include "lauxlib.h" #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <string.h> typedef

快速掌握Lua 5.3 —— 调试库 (2)

Q:如何调试"Closure"的"upvalue"信息? A: --[[ debug.getupvalue(f, up) 返回函数("Closure")"f"的第"up"个"upvalue"的名字和值. Lua按照"upvalues"在匿名函数中出现的顺序对其编号.如果指定的"up"索引越界,则返回"nil". 以'('开头的变

快速掌握Lua 5.3 —— 调试库 (1)

Q:什么是活动函数? A:程序中被调用但还未执行完成的函数. function g() --[[ 此时函数"g"被调用但还未执行完成,是活动函数.所以这里获取的是函数"g"的信息. "debug.getinfo(2)"获取的才是函数"f"的信息.]] local x = debug.getinfo(1, "n") for k, v in pairs(x) do print(k, v) end end fun

Lua的Full UserData、Light UserData和metatable

http://lua.2524044.n2.nabble.com/LightUserData-and-metatables-td3807698.html "... do you realize that by setting the metatable of a light userdatayou are actually setting the metatable of all light userdata at once ?"I did not realise this. oops