yanbin's Blog

Lua 调用的 C 函数保存 state 的两种方式: Storing State in C Functions 笔记

使用 Lua call C 编程,不免需要保存一些 non-local data 在 C 函数中; non-local data 来自 Lua state.
然而,直接使用 C static/global 变量保存 state 是有问题的: (a) C 变量不能保存一个 generic Lua value;
(b) static/global 变量的方式也无法用于 multiple Lua states, 因为不同 Lua state 调用相同函数/库时,
需要 C 函数保存的 state 是独立不相关的。
 
C API 提供了两个地方来保存 non-local data: registry 和 upvalues.
 
Registry
Registry 是一个 global table, 只能在 C 函数中使用 Lua C API 提供的函数和 pseudo-index 访问。
pseudo-index 就像一个普通的 stack index 一样,不同的是它所关联的 value 并不真正的存放在 stack 之上。
访问 registry 的 psedu-index 定义 LUA_REGISTRYINDEX. 对 registry 的访问就像是对 stack 访问一样,
Lua C API 提供的大多数函数都可以用于 registry —— 指定 LUA_REGISTRYINDEX 作为 index ——,
只有 lua_remove(), lua_insert() 这些操作 stack 本身的函数除外。例如:
lua_getfiled(L, LUA_REGISTRYINDEX, "SOME_KEY");
 
既然 registry 是一个 regular table, 那么就可以用所有 index 访问它,当然 nil 是不行的。
使用 registry  index 的选用一定要谨慎,毕竟 registry 是全局的,而且所有库和函数都可以共享的 table;
为避免 key 冲突,应当:
(a) 不要使用 number 作为 key;
(b) 使用 string 作为 key, 尽量加上一些特殊的前缀,比如: LIBRAY_NAME_ 这样的;
(c) 使用 reference system;
 
Reference system 是 auxilibrary 提供的一系列函数,使用这些函数可以保证存储 value 到 table
无须关心 key 的唯一性。如下方式可创建一个新的 reference:
int r = luaL_ref(L, LUA_REISTRYINDEX);
这个函数 pop 一个 value 从 stack, 并用新的 index 保存到指定的 table 中,这里 LUA_REISTRYINDEX
关联到 registry, 所以这个调用可保存一个 value 到 registry.
luaL_ref() 函数会将 key 返回,用于以后的对 table 的访问以及 release reference.
luaL_rawgeti(L, LUA_REGISTRYINDEX, r);
luaL_unref(L, LUA_REGISTRYINDEX, r);
 
指定给 luaL_ref() 的 value 是 nil —— 或者说栈顶的 valu 为 nil ——,luaL_ref() 返回 LUA_REFNIL,
使用 LUA_REFNIL 调用 luaL_unref() 和 lua_rawgeti() 都没有任何效果,算得上人畜无害。
另外一个 reference system 定义的常量是 LUA_NOREF, 这个常量保证不同于任何 valid reference,
可用来判断一个 reference 是否为 invalid reference.
 
reference system 有用之处就在于: 我们不能使用 C pointers 指向任何一个 lua object(包括 string),
这样的引用是未定义行为,但是我们可以使用 reference system 将对象存储在 registry 中,
并且维护这个 reference, 就像使用 C pointer 一样。
 
另外一种选择 key 的方式是:使用 C library 中的 static 变量的地址作为 key(的创建源)。
C 链接器可以保证这些变量的地址是唯一的, 常量的大概不行,比如:字符串常量,宏定义的常量。
不过直接用 static 变量的地址作wei index 是错的,得玩点黑魔法:
lua_pushlightuserdata();或 lua_rawsetp(), lua_rawgetp();
 
示例代码:
/* variable with a unique address */
static char key = 'k';
/* store a string */
lua_pushlightuserdata(L, (void *)key); /* push address */
lua_pushstring(L, myStr); /* push value */
lua_settable(L, LUA_REGISTRYINDEX); /* registry[&kye] = myStr */
/* retrieve a string */
lua_pushlightuserdata(L, (void *)key); /* push address */
lua_gettable(L, LUA_REGISTRYINDEX); /* retrieve value */
const char *myStr = lua_tostring(L, -1); /* convert to C string */
 
使用 lua_rawsetp(), lua_rawgetp() 的方式
/* variable with a unique address */
static char key = 'k';
/* store a string */
lua_pushstring(L, myStr);
lua_rawsetp(L, LUA_REGISTRYINDEX, (void *)&key);
/* retrieve a string */
lua_rawgetp(L, LUA_REGISTRYINDEX, (void *)&key);
const char *myStr = lua_tostring(L, -1);

以上这些创建 key 以访问 table 规则对普通 table 也适用。
 
 
Upvalues
Registry 提供了 gblobal 变量给 C function 而 upvalues 机制更象是提供了 static 变量给 C function.
Upvalues 机制主要实现了将 upvalues 与 C functions 相关联,每一个 upvalue 可以保存一个 Lua value.
主要方式是使用 C function 创建 C closure, 并且提供 upvalues 给 C closure.
C closure 的使用与 Lua closure 非常相似,实际 Lua 代码中就是当做 closure 来使用的。
Upvalues 机制可以使用相同的 C function clode 以及不用 upvalues 创建多个不同的 closure.
 
示例代码:
首先是一个工厂函数,这个函数用来创建 C closure.
static int counter(lua_State *L);
int newCounter(lua_State *L)
{
     lua_pushinteger(L, 0);
     /* 使用 counter 作为 base function 创建 C closure, 讲 stack TOP N 作为 upvalues */
     lua_pushcclosure(L, &counter, 1);
     return 1;
}
 
黑魔法函数是 lua_pushcclosure(), 其第二个参数制定用来创建 C closure 的 base function,
第三个参数指定 stack TOP N 作为 upvalues.
NOTE TAHT:必须保证 stack 中前 N 个 value 是要作为 upvalues 的,不然是错误。
 
看看 base function 中是如何访问 upvalues 的。
static int counter(lua_State *L)
{
     int val = lua_tointeger(L, lua_upvalueindex(1));
     lua_pushinteger(L, ++val);
     lua_pushvalue(L, -1); /* duplicate new vlaue */
     lua_replace(L, lua_upvalueindex(1));
     return 1;
}
 
访问 upvalues 也是用 pseudo-index 的方式,真正访问的值并不保存在 stack.
lua_upvalueindex(N) 访问一个 pseudo-index 关联于第 N 个 upvalue。
NOTE THAT:
绝对不能用负数访问 lua_upvalueindex(), 为了避免这样错误可以用 luaL_argcheck() 函数检查参数。
 
 
Shared upvalues
C closure 间不能像 Lua closure 那样共享 upvalues;
为了做到这一点可以让 C closure 间共享一个 table 作为 upvalue, 从而共享了 upvalues.
具体黑魔法要先看 luaL_newlib() 的定义, 这是一个宏定义:
#define luaL_newlib(L, lib) \
     (luaL_newlibtable(L, lib), luaL_setfuncs(L, lib, 0))
关键点是 luaL_setfuncs() 这个函数讲 statck top N 个 value 作为 upvalues 指定给 library 中的函数。
luaL_newlib() 定义中这个 N 是 0, 这个函数是用不成了。不过可以用如下方式:
/* create library table  */
luaL_newlibtable(L, lib);
/* create shared value */
lua_newtable(L);
/* add functions in list 'lib' to the new library,
* sharing previous table as upvalue
*/

luaL_setfuncs(L, lib, 1);

 

参考:
Programming in Lua third edition 28.3
 

为什么使用 do {} while(0)

有些宏定义含有多行代码,一般使用 do {} while(0) 把代码放在 'do' 和 'while' 之间的 '{}' 中。

#define foorbar(msg, callback) do {\
         struct Task __task = msg_to_task((msg)); \
           if (__task != NULL) { \
              process(__task, (callback)); \
              char *__msg = task_to_msg(__task); \
              if (__msg != NULL) \ {
                 send_msg(__msg); \
                 free(__msg); \
              } \
              destroy_task(__task); \
           } while (0)

这样用的原因是:
1.符合 C/C++ 语法习惯。
每条语句后面都一个';', 而 do {} while 语句后面也要加一个 ';'.

2.避免出现语法错误。
不用 do {} while(0) 把多行代码包起来,在 if else 语句中就会有语法错误,例如:

#define foorbar(a, b) foor((a)); bar((b))
if (something)
  /* 以下有语法错误 */
  foorbar(3, 2);
else
  // do something

仅仅使用 '{}' 把多行代码包起来,如果在调用宏之后加 ';', 也会有语法错误。

#define foorbar(a, b) {\
           foor((a)); bar((b));\
       }
foorbar(3, 2); // 此处有语法错误

/* 编译器提示:
 * error: expected ‘;’ before ‘}’ token
 * 如果不加 ';', 不会有语法错误但是这样不符合 C/C++ 的语法习惯
 */

3.do {} while(0) 可以根据条件跳出执行。

#define foorbar() do {\
        struct condition __cond; \
        if (__cond.wait_cond()) \
           break; // 条件发生退出执行 \

        // 条件没有发生 do something 
      } while(0)

 

4.私以为 do {} while(0) 可以保证代码执行并且只执行一次。

5.需要注意的地方。
(a)宏定义时用 '\' 连接多行语句;
(b)宏定义中定义变量,注意与外部变量名字冲突,不然原本希望用外面的变量,却用了新定义的变量。
(c)有些编译器会优化掉 do {} while(0); 直接展开 '{}' 内的代码, 如(b)所描述,此时会出现语法错误。
   FIXME: 如果内部有 'break' 并且 'break' 的执行依赖运行时条件,编译器就不会优化掉 do {} while(0); 了。
举例:

#define foorbar() do {\
        struct condition cond; \
        if (cond.wait_cond()) \
           break; // 条件发生退出执行 \

        // 条件没有发生 do something 
      } while(0)

struct condition cond;
// do something
foobar(); // 到底用的是哪一个 cond?
#define foorbar(a, b) do {\
         const char *something = get_something(a, b); \
      } while(0)

const char *something;
// do something

foorbar(3, 9); // 如果编译器优化掉了 do {} while(0); 这里有语法错误。

 

感谢 老猫,mike2,MovableType@源赖朝 三位网友。

参考:
do { … } while (0) — what is it good for?
do{}while(0) 的作用



 




Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee