如何检测一个userdata是否有“环境表”?

在Lua 5.1中,可以将_environment_表与userdata关联。这样,我们就可以为每个userdata添加“字段”。

显然的方法是在创建userdata时创建此环境表,可能是一个空表。但是,在我的应用程序中,将存在许多userdata,并不一定需要此环境表,并且它们经常存在和消失。我不希望创建许多不会使用的临时空表。

因此,我想只在检测到userdata没有由我创建的环境表时才创建它。

问题在于,在Lua 5.1中,默认的环境表不是nil,而是全局表_G(我不知道这在地球上有什么用处)。因此,我会这样测试是否为非初始化的userdata:

/* 'index' is where my userdata is */
lua_getfenv(L, index);
initialized = lua_rawequal(L, index, LUA_GLOBALSINDEX);

现在,我的问题:

我需要使用LUA_ENVIRONINDEX还是LUA_GLOBALSINDEX?还是我需要做其他什么?此测试将在任何情况下运作吗?

点赞
用户3677376
用户3677376

通常情况下,您会遇到全局表 _Gpackage 表作为默认环境。这取决于调用 lua_newuserdata 的 C 函数的函数环境(请参阅这里)。通过 require 加载的扩展模块中的用户数据通常具有 package 表作为默认环境,因为 require 已将该表设置为环境,并且该表被所有注册的 C 函数和在其中创建的用户数据继承。另一方面,newproxy_G 设置为默认环境。

原则上,如果某人更改 C 函数的函数环境(这很少见),则可以获取任何表格作为默认环境。实际上,只检查 _Gpackage 表格可能已足够。为此,您需要用户数据环境表、package 表和全局表的有效索引:

lua_getfenv(L, index); /* 将ud的环境压入栈中 */
lua_getglobal(L, "package"); /* 将package表格压入栈中 */
/* 全局表在LUA_GLOBALSINDEX处始终可用,因此不需要压入任何内容... */
hasdefaultenv = lua_rawequal(L, -2, -1)
             || lua_rawequal(L, -2, LUA_GLOBALSINDEX);

您可能希望将 package 表格保存在某个地方(例如内部变量)中,以防止任何人通过替换 package 表格来破坏您的测试。

然而,最强大的解决方案是在创建用户数据时设置一个唯一的共享虚拟表作为环境(您可以使用 _G),并在必要时检查并替换该虚拟表。

另外,LUA_ENVIRONINDEX 用于获取当前运行的 C 函数的环境,而不是获取用户数据的环境。

2014-05-26 19:42:57