Lua 省略号表达式限制为 248

Lua Ellipsis Expression limited at 248

我正在为一所大学 Lua 学习移动应用程序开发 class,最近我们介绍了允许动态数量参数的省略号运算符 (...)。出于好奇,我决定尝试找出它可以处理的参数数量是否有限制,结果是 248.

例如:

function pr(...)
    print(...)
end

pr(1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1)

我认为它可能 OS 依赖于堆栈大小或与堆栈大小相关,所以我在 Linux 和 Windows 上分别测试了它的 32 位和 64 位版本。允许的元素数量保持在 248。这似乎是一个硬编码限制。如果我尝试 > 248 那么我得到的错误是:

main.lua:30 function or expression too complex near <eof>

我无法想象在大多数情况下有人需要超过 248 个表达式,但对于那些情况,有没有办法解决这个问题?另外,为什么是248?这个数字好像没什么特别的。

堆栈帧限制

您遇到的限制并不直接与允许的函数参数的最大数量有关。它大约是 Lua 堆栈框架的最大大小。

以下示例对此进行了演示。 Lua 中的局部变量也用完了函数堆栈帧中的槽 1 并且通过声明 200 个局部变量,我们现在只需要 print 函数中的 48 个参数即可达到限制:

local x001,x002,x003,x004,x005,x006,x007,x008,x009,x010
local x011,x012,x013,x014,x015,x016,x017,x018,x019,x020
local x021,x022,x023,x024,x025,x026,x027,x028,x029,x030
local x031,x032,x033,x034,x035,x036,x037,x038,x039,x040
local x041,x042,x043,x044,x045,x046,x047,x048,x049,x050
local x051,x052,x053,x054,x055,x056,x057,x058,x059,x060
local x061,x062,x063,x064,x065,x066,x067,x068,x069,x070
local x071,x072,x073,x074,x075,x076,x077,x078,x079,x080
local x081,x082,x083,x084,x085,x086,x087,x088,x089,x090
local x091,x092,x093,x094,x095,x096,x097,x098,x099,x100
local x101,x102,x103,x104,x105,x106,x107,x108,x109,x110
local x111,x112,x113,x114,x115,x116,x117,x118,x119,x120
local x121,x122,x123,x124,x125,x126,x127,x128,x129,x130
local x131,x132,x133,x134,x135,x136,x137,x138,x139,x140
local x141,x142,x143,x144,x145,x146,x147,x148,x149,x150
local x151,x152,x153,x154,x155,x156,x157,x158,x159,x160
local x161,x162,x163,x164,x165,x166,x167,x168,x169,x170
local x171,x172,x173,x174,x175,x176,x177,x178,x179,x180
local x181,x182,x183,x184,x185,x186,x187,x188,x189,x190
local x191,x192,x193,x194,x195,x196,x197,x198,x199,x200
print(
1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1
)

这也是错误消息显示为 function or expression too complex 而不是 too many arguments passed to function 或类似内容的原因。

为什么是 248?

堆栈帧的最大大小实际上是 249。在 llimits.h 中,Lua 将 MAXSTACK 定义为 250,而在 lcode.c 中,checkstack 函数仅允许 比那个小..

 if (newstack >= MAXSTACK)
      luaX_syntaxerror(fs->ls, "function or expression too complex");

这与我们在实验中得到的结果相符。函数调用的字节码 f(a1,a2,...,an) 需要堆栈上的 N+1 寄存器:一个指向我们正在调用的函数,N 指向函数的参数,248+1 = 249 < = 250。(如果使用函数的 return 值,我们还需要额外的寄存器)

根据 Roberto Ierusalimschy 的说法,250 这个相对较小的限制是出于性能原因。将堆栈帧大小保持在 256 以下意味着 only 8 bits are needed to store a stack frame offset,并且由于每个 Lua 字节码包含 2 或 3 个这些堆栈偏移量作为参数,因此使用更少的字节来存储偏移量非常重要。

如何将超过 250 个参数传递给一个函数

回到您最初的问题,实际上可以将超过 256 个参数传递给可变参数函数。如果不是使用大量逗号分隔的参数,而是解压缩 table 或使用其他函数 return 的多个结果,那么我们必须面对的限制是整个 Lua 的大小堆栈而不是单个堆栈帧的大小。此限制 (LUA_MAXSTACK) 可由用户在 luaconf.h 中配置,默认情况下为 1000000。

function rep(n)
  local t = {}
  for i = 1, n do
    t[i] = i
  end
  return t
end

function foo(...)
end

foo(table.unpack(rep(999986))) -- Pretty close to 1000000

1:请记住,Lua 脚本的整个主体的行为就好像它在函数内部一样,因此 Lua "toplevel"仍受 "stack frame" 限制。