MONTH / April, 2013

C/C++ 中遍历 Lua table 完整版

在 C/C++ 中遍历一个 Lua table用 lua_next 来实现,这个大家都知道。然而,我却看到很多文章在示范 lua_next 时都只是点到为止,或绝口不提如何获取 key 值,或直接定义该 table 的 key 都是非匿名的,从而简单粗暴地使用 lua_tostring 来获取值。

仔细看看,Lua manual 里对 lua_next 的说明中最后有一句很重要的话:

While traversing a table, do not call lua_tolstring directly on a key, unless you know that the key is actually a string. Recall that lua_tolstring changes the value at the given index; this confuses the next call to lua_next.

遍历 table 的过程中不要直接对处于 -2 位置的 key 做 lua_tostring 操作(还记得这个函数说了它会原地修改栈上的值的吧),除非你确信现在这个 key 就是字符串类型,否则下一次执行 lua_next 就等着意外吧。

好吧,来个完整版,其实不就是把 key 拷贝一份到栈上,然后对这份拷贝进行取值么[1]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <lauxlib.h>
#include <lua.h>
 
void traverse_table(lua_State *L, int index)
{
    lua_pushnil(L); 
    // 现在的栈:-1 => nil; index => table
    while (lua_next(L, index))
    {
        // 现在的栈:-1 => value; -2 => key; index => table
        // 拷贝一份 key 到栈顶,然后对它做 lua_tostring 就不会改变原始的 key 值了
        lua_pushvalue(L, -2);
        // 现在的栈:-1 => key; -2 => value; -3 => key; index => table
        const char* key = lua_tostring(L, -1);
        const char* value = lua_tostring(L, -2);
 
        printf("%s => %s\n", key, value);
 
        // 弹出 value 和拷贝的 key,留下原始的 key 作为下一次 lua_next 的参数
        lua_pop(L, 2);
        // 现在的栈:-1 => key; index => table
    }
    // 现在的栈:index => table (最后 lua_next 返回 0 的时候它已经把上一次留下的 key 给弹出了)
    // 所以栈已经恢复到进入这个函数时的状态
}
 
int main()
{
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
 
    // 假设现在栈上已经有了一个 talbe,内容为 {one=1,[2]='two',three=3},位置在 top
 
    traverse_table(L, -1);
 
    return 0;
}

[1] Iterate through Lua Table


神奇的 bool

今天跟同事一起debug,发现一个神奇的事情,在 VC10 的调试器里,两个 bool 变量都是 true,但是程序执行时却走了它们不相等的路径。来看代码:

1
2
3
4
5
6
7
8
9
10
class Foo
{
public:
    Foo(void) {}
 
    bool Bar(bool flag) { return flag == _flag; }
 
private:
    bool _flag;
}

断点下在 Bar() 函数内,调试器显示两个变量都是 true,但是返回的值却是 false!没错!程序 rebuild 过了,眼镜也擦过了!

我会告诉你 Foo::_flag 忘了初始化了么?

噢,对啊。但是,当时调试的时候,他们都是 true 啊!!为什么不相等?让我们来翻看 C++ 标准文档。

关于 bool 类型,3.9.1/7 里是这么说的:

Types bool, char, char16_t, char32_t, wchar_t, and the signed and unsigned integer types are collectively called integral types.

说白了,bool 也是个数。(我一直以为 bool 类型会什么特殊优化)

关于 sizeof(),5.3.3/1 里是这么说的:

sizeof(char), sizeof(signed char) and sizeof(unsigned char) are 1. The result of sizeof applied to any other fundamental type (3.9.1) is implementation-defined. [ Note: in particular, sizeof(bool), sizeof(char16_t), sizeof(char32_t), and sizeof(wchar_t) are implementation-defined. *74 ]
*74: sizeof(bool) is not required to be 1.

除了 char 的大小,其他所有基础类型的大小都是由实现决定。由!实!现!决!定!(C++ 你就把我往死里坑吧)

而就我所知的编译器(其实我只知道两个),bool 默认都是一个字节。所以从内存的角度来说,它完全可以表示 254 个非“零”值。那么,一个值为 1 的 bool 变量和一个值为 100 的 bool 变量,他们相等么?上面都说了,bool 类型是数!字!类!型!所以,bool 变量间的比较,其行为和其他数字类型的应该保持一致,也就是进行纯粹的数值比较(true、false 这两个关键字应该作为 1 和 0 的别名,而不是一种新的语义)。

微软似乎也发现了这个秘密(我有告诉过他们么?),所以在 VS2012 里,调试器会把非 0 且非 1 的 bool 变量的具体值显示出来,而不再是简单的判断一个 bool 值是否是非零了。

泪奔吧,骚年!