searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

lua数组中使用nil值的坑

2023-08-04 06:50:53
62
0

lua数组中使用nil值,可能会导致意想不到的结果,浅析其中原因及解决办法。

先看以下lua代码用例

local t = {1, 2, 3, 4, 5, 6}
print("Test1 " .. #t)

t[6] = nil
print("Test2 " .. #t)

t[4] = nil
print("Test3 " .. #t)

t[2] = nil
print("Test4 " .. #t)

我们使用LuaJIT 2.1执行这个用例,结果如下

# luajit test.lua
Test1 6
Test2 5
Test3 3
Test4 1

使用Lua 5.1.4执行这个用例,结果如下

# lua test.lua
Test1 6
Test2 5
Test3 3
Test4 3

可以发现,我们本意是想删除数组中的元素,每次调用完数组长度应该是减1,但测试结果却令人匪夷所思。这是为什么呢。

查看官方手册,这里直接引用 Lua 5.1 manual 上的原话:

The length of a table t is defined to be any integer index n such that t[n] is not nil and t[n+1] is nil; moreover, if t[1] is nil, n can be zero. For a regular array, with non-nil values from 1 to a given n, its length is exactly that n, the index of its last value. If the array has "holes" (that is, nil values between other non-nil values), then #t can be any of the indices that directly precedes a nil value (that is, it may consider any such nil value as the end of the array).

总结就是,对于常规数组,里面从1到n放着一些非空的值的时候,它的长度就精确的为 n。但如果数组中间被掏空,即 nil 值被夹在非空值之间,那么任意 nil 值前面的索引都有可能是 # 操作符返回的值,所以 #t 的结果才这么奇怪。

除了 # 操作符结果异常外,所有受带nil数组的长度影响的操作都有可能出问题,比如说unpacktable.getnipairs

  • table.getn:同 # 操作符

  • ipairs:遍历的时候遇到任意 nil 可能导致后面的元素没遍历到

  • unpack:遇到 nil 返回值会缺失

所以在lua代码中尽量不要在数组中使用nil,值得一提的是,这些无法预估的现象在测试中是相对难发现的,这些代码跑在生产环境中是个不小的隐患。

解决方法有以下:

  1. 使用table.remove来删除数组元素,取代赋值nil。注意的是,remove之后数组长度发生变化,同时改变的还有数组元素的索引,比如删掉第二个元素后,使用t[2]才能索引到原来的t[3]。有这种指定索引值的场景需要注意一下。

  2. 对于空数据,不适用nil,使用别的约定的值去替代。ngx_lua里有两个比较适合的候选:false和ngx.null。这种方式实际上就是用了个约定的“占位符”来替代nil,因此数组长度是不变的,索引也不会发生变化。

    • false:在lua里false也是个假值,所以涉及到if conditionres or default_value相关代码也不需要调整。

    • ngx.null:如果false本身可能是个返回值,就考虑用ngx.null。但注意ngx.null是个真值,相关代码可能需要调整,需要自己在代码中判断if res ~= ngx.null,来判断是否是空数据   

0条评论
0 / 1000
discolor
3文章数
0粉丝数
discolor
3 文章 | 0 粉丝
discolor
3文章数
0粉丝数
discolor
3 文章 | 0 粉丝
原创

lua数组中使用nil值的坑

2023-08-04 06:50:53
62
0

lua数组中使用nil值,可能会导致意想不到的结果,浅析其中原因及解决办法。

先看以下lua代码用例

local t = {1, 2, 3, 4, 5, 6}
print("Test1 " .. #t)

t[6] = nil
print("Test2 " .. #t)

t[4] = nil
print("Test3 " .. #t)

t[2] = nil
print("Test4 " .. #t)

我们使用LuaJIT 2.1执行这个用例,结果如下

# luajit test.lua
Test1 6
Test2 5
Test3 3
Test4 1

使用Lua 5.1.4执行这个用例,结果如下

# lua test.lua
Test1 6
Test2 5
Test3 3
Test4 3

可以发现,我们本意是想删除数组中的元素,每次调用完数组长度应该是减1,但测试结果却令人匪夷所思。这是为什么呢。

查看官方手册,这里直接引用 Lua 5.1 manual 上的原话:

The length of a table t is defined to be any integer index n such that t[n] is not nil and t[n+1] is nil; moreover, if t[1] is nil, n can be zero. For a regular array, with non-nil values from 1 to a given n, its length is exactly that n, the index of its last value. If the array has "holes" (that is, nil values between other non-nil values), then #t can be any of the indices that directly precedes a nil value (that is, it may consider any such nil value as the end of the array).

总结就是,对于常规数组,里面从1到n放着一些非空的值的时候,它的长度就精确的为 n。但如果数组中间被掏空,即 nil 值被夹在非空值之间,那么任意 nil 值前面的索引都有可能是 # 操作符返回的值,所以 #t 的结果才这么奇怪。

除了 # 操作符结果异常外,所有受带nil数组的长度影响的操作都有可能出问题,比如说unpacktable.getnipairs

  • table.getn:同 # 操作符

  • ipairs:遍历的时候遇到任意 nil 可能导致后面的元素没遍历到

  • unpack:遇到 nil 返回值会缺失

所以在lua代码中尽量不要在数组中使用nil,值得一提的是,这些无法预估的现象在测试中是相对难发现的,这些代码跑在生产环境中是个不小的隐患。

解决方法有以下:

  1. 使用table.remove来删除数组元素,取代赋值nil。注意的是,remove之后数组长度发生变化,同时改变的还有数组元素的索引,比如删掉第二个元素后,使用t[2]才能索引到原来的t[3]。有这种指定索引值的场景需要注意一下。

  2. 对于空数据,不适用nil,使用别的约定的值去替代。ngx_lua里有两个比较适合的候选:false和ngx.null。这种方式实际上就是用了个约定的“占位符”来替代nil,因此数组长度是不变的,索引也不会发生变化。

    • false:在lua里false也是个假值,所以涉及到if conditionres or default_value相关代码也不需要调整。

    • ngx.null:如果false本身可能是个返回值,就考虑用ngx.null。但注意ngx.null是个真值,相关代码可能需要调整,需要自己在代码中判断if res ~= ngx.null,来判断是否是空数据   

文章来自个人专栏
静态网关
3 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
1
0