版本
lua-5.1.5
疑问
lua获取table长度的接口有很多:
- table.getn()
- table.maxn()
- ’#’操作符
推荐一篇文章:浅析Lua中table的遍历
但是也会出现神奇的情况,请看下面一小段代码:
local t = {111, x = 222, nil, 333, [10] = 555, {}, nil, nil}
print(table.getn(t)) -- 4
print(#t) -- 4
print(table.maxn(t)) -- 10
--local t = {111, nil, x = 222, nil, 333, [10] = 555, {}, nil, nil}
--local t = {111, x = 222, nil, 333, [10] = 555, nil, {}, nil, nil}
如果随机在t中插入nil,结果会让你大吃一惊,丈二和尚摸不着头脑。比如:
- 在111后面插入一个nil,结果是: 1, 1, 10
- 在555后面插入一个nil,结果却是:5, 5, 10
这是为什么呢?有没有规律呢?
table的#操作符
这里原因我先简单介绍一下,想弄更清楚的请自己看完之后看源代码,算是抛砖引玉^_^!上面的结果千奇百怪主要是和#操作符有关,#操作符最终获取table长度的函数是(文件ltable.c中):
/*
** Try to find a boundary in table `t'. A `boundary' is an integer index
** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
*/
int luaH_getn (Table *t) {
unsigned int j = t->sizearray;
if (j > 0 && ttisnil(&t->array[j - 1])) {
/* there is a boundary in the array part: (binary) search for it */
unsigned int i = 0;
while (j - i > 1) {
unsigned int m = (i+j)/2;
if (ttisnil(&t->array[m - 1])) j = m;
else i = m;
}
return i;
}
/* else must find a boundary in hash part */
else if (t->node == dummynode) /* hash part is empty? */
return j; /* that is easy... */
else return unbound_search(t, j);
}
只看很小的一部分:
while (j - i > 1) {
unsigned int m = (i+j)/2;
if (ttisnil(&t->array[m - 1])) j = m;
else i = m;
}
return i;
unbound_search函数中也有一部分逻辑和上面代码一样
知道二分查找的人花一点时间应该很快能够理解这段代码的意思,从数组[i-j]中二分查找的方式遍历,如果不为ni的话,相当于长度增加了(i=m);如果为nil的话,相当于长度减少了,也举个简单的例子:
local t = {1, 2, nil}
print("#t1 = ", #t1)
--[[
i=0, j=3
(1)m=1, array[m-1]=1不为nil, i=m=1
(2)m=2, array[m-1]=2不为nil, i=m=2
(3)j-i <=1循环结束,return i,结果为2
]]
local t = {1, nil, 2, nil}
print("#t1 = ", #t1)
--[[
i=0, j=4
(1)m=2, array[m-1]=nil, j=m=2
(2)m=1, array[m-1]=1不为nil, i=m=1
(3)j-i <=1循环结束,return i,结果为1
]]
可以看到,ni的位置不确定性,让返回的长度变幻莫测了,这也就是为什么上面看到的结果千奇百怪了 ^_^
总结
- lua的’#’操作符获取的长度在table中间有nil的情况很不稳定
- 虽然看到代码还是有一定规律可循的,但我个人认为也不应该把这个规律用来计算这种table的长度
- 再回来看luaH_getn函数开头的一小段代码解释说明,会更容易理解一点:’Try to find a boundary in table
t'. A
boundary’ is an integer index such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).’
拓展
1.unpack({…})
我们会经常用到这种方式,比如传进来的参数是不定长的’…‘,然后用table的操作符’{}’括起来,变成一个table,然后使用unpack解包。恰好,前两天有个同事跟我们分享了这个使用方式的漏洞。一开始有点吃惊,看完上面的一些分析只会,也就能够明晰许多了。下面是同事的分享。
问题代码
function fnUnPack( ... )
local p = {...};
return unpack(p);
end
print(fnUnPack(1, 2, 3));
print(fnUnPack(1, 2, 3, nil ,4));
print(fnUnPack(1, 2, 3, nil ,4, nil));
--结果
1 2 3
1 2 3 nil 4
1 2 3
解决办法
- (1)使用table的pack和unpack
示例代码:
function fnUnPack1( ... )
local p = table.pack(...);
return table.unpack(p, 1, p.n);
end
版本lua-5.2及以上
- (2)使用select
示例代码:
function fnUnPack2( ... )
local p = {...}
local n = select('#', ...)
return unpack(p, 1, n);
end
2.版本5.2及以上
- lua-5.2以上已经没有table.getn和table.maxn接口了