在 Codea
上運行其餘人之前寫的代碼時, 發現某段處理 touch
事件的代碼老是報錯, 開始報浮點數沒有整型的表示, 修改代碼增長類型轉換後, 又報越界錯誤.git
由於這些程序在以前版本的 Codea
能夠正常運行(使用 lua-5.1), 因此我推測這個錯誤多是 lua
版本差別引起的. 爲方便定位問題, 從 iPad
轉到 樹莓派
的 lua-5.3.2
環境進行試驗(由於目前最新版本的 Codea 對應的 Lua 版本是 5.3), Codea
中的試驗代碼以下:數組
touches = {} touch={id=100} table.insert(touches, math.floor(touch.id), touch)
在 Lua-5.3.2
中報錯, 運行信息以下:ide
pi@rpi /opt/software/lua-5.3.2 $ lua Lua 5.3.2 Copyright (C) 1994-2015 Lua.org, PUC-Rio > > touches = {} > touch={id=100} > table.insert(touches, math.floor(touch.id), touch) stdin:1: bad argument #2 to 'insert' (position out of bounds) stack traceback: [C]: in function 'table.insert' stdin:1: in main chunk [C]: in ? >
在 Lua-5.1.5
中正常運行, 運行信息以下:函數
pi@rpi /opt/software $ lua5.1 Lua 5.1.5 Copyright (C) 1994-2012 Lua.org, PUC-Rio > > touches = {} > touch={id=100} > table.insert(touches, math.floor(touch.id), touch) >
在 Lua-5.3.2
中報錯, 運行信息以下:優化
kano@kano ~ $ lua Lua 5.3.2 Copyright (C) 1994-2015 Lua.org, PUC-Rio > my={} > table.insert(my,123,12) stdin:1: bad argument #2 to 'insert' (position out of bounds) stack traceback: [C]: in function 'table.insert' stdin:1: in main chunk [C]: in ? > table.insert(my,1,12) > table.insert(my,2,12) > table.insert(my,4,12) stdin:1: bad argument #2 to 'insert' (position out of bounds) stack traceback: [C]: in function 'table.insert' stdin:1: in main chunk [C]: in ? > my[123]=123 > #my 2 > unpack(my) stdin:1: attempt to call a nil value (global 'unpack') stack traceback: stdin:1: in main chunk [C]: in ? > table.unpack(my) 12 12 > for k,v in pairs(my) do print(k,v) end 1 12 2 12 123 123 >
再看看 5.1
中的表現ui
pi@rpi /opt/software/lua-5.3.2/src $ lua Lua 5.1.5 Copyright (C) 1994-2012 Lua.org, PUC-Rio > my={} > table.insert(my,123,12) > #my stdin:1: unexpected symbol near '#' > table.length(my) stdin:1: attempt to call field 'length' (a nil value) stack traceback: stdin:1: in main chunk [C]: ? > table.len(my) stdin:1: attempt to call field 'len' (a nil value) stack traceback: stdin:1: in main chunk [C]: ? > table.insert(my,1,12) > table.insert(my,2,12) > table.insert(my,4,12) > my[123]=123 > print(#my) 4 > table.unpack(my) stdin:1: attempt to call field 'unpack' (a nil value) stack traceback: stdin:1: in main chunk [C]: ? > for k,v in pairs(my) do print(k,v) end 1 12 2 12 4 12 123 123 >
能夠看出:lua
table.insert
時空表必須從 1
開始, 後面的索引要跟前一個保持連續.123
僅僅被當成 my
中哈希表的 key
, 而不是數組索引.開始懷疑多是 touch.id
數字太大, 後來發現改用小數字也不行, 幸虧 lua
提供了源代碼, 用 git grep -n "報錯信息"
在 lua-5.3.2
的源代碼中順利找到對應的函數代碼, 發現確實有一個條件判斷, 查詢結果以下:code
pi@rpi /opt/software/lua-5.3.2 $ git grep -n "position out of bounds" src/ltablib.c:90: luaL_argcheck(L, 1 <= pos && pos <= e, 2, "position out of bounds"); src/ltablib.c:110: luaL_argcheck(L, 1 <= pos && pos <= size + 1, 1, "position out of bounds"); pi@rpi /opt/software/lua-5.3.2 $
查詢結果很明確, 該錯誤信息可在源文件 src/ltablib.c
的第 90
行和第 110
行找到, 用 vi
打開該文件, 在 vi
命令模式下輸入 :90
, 便可跳轉到第 90
行, 發現是一個 table.insert
函數, 第 110
行是一個 table.remove
函數, 代碼以下:索引
79 static int tinsert (lua_State *L) { 80 lua_Integer e = aux_getn(L, 1, TAB_RW) + 1; /* first empty element */ 81 lua_Integer pos; /* where to insert new element */ 82 switch (lua_gettop(L)) { 83 case 2: { /* called with only 2 arguments */ 84 pos = e; /* insert new element at the end */ 85 break; 86 } 87 case 3: { 88 lua_Integer i; 89 pos = luaL_checkinteger(L, 2); /* 2nd argument is the position */ 90 luaL_argcheck(L, 1 <= pos && pos <= e, 2, "position out of bounds"); 91 for (i = e; i > pos; i--) { /* move up elements */ 92 lua_geti(L, 1, i - 1); 93 lua_seti(L, 1, i); /* t[i] = t[i - 1] */ 94 } 95 break; 96 } 97 default: { 98 return luaL_error(L, "wrong number of arguments to 'insert'"); 99 } 100 } 101 lua_seti(L, 1, pos); /* t[pos] = v */ 102 return 0; 103 } 104 105 106 static int tremove (lua_State *L) { 107 lua_Integer size = aux_getn(L, 1, TAB_RW); 108 lua_Integer pos = luaL_optinteger(L, 2, size); 109 if (pos != size) /* validate 'pos' if given */ 110 luaL_argcheck(L, 1 <= pos && pos <= size + 1, 1, "position out of bounds"); 111 lua_geti(L, 1, pos); /* result = t[pos] */ 112 for ( ; pos < size; pos++) { 113 lua_geti(L, 1, pos + 1); 114 lua_seti(L, 1, pos); /* t[pos] = t[pos + 1] */ 115 } 116 lua_pushnil(L); 117 lua_seti(L, 1, pos); /* t[pos] = nil */ 118 return 1; 119 }
讀讀代碼, 發現這裏的兩個函數都用 luaL_argcheck
對參數作了檢查, 若是合法則經過, 若是不合法則返回錯誤信息.事件
在函數 tinsert
中的合法條件是 1 <= pos && pos <= e
, 那麼 e
是多少呢? 繼續看代碼, 在函數最開始有定義, 還有註釋:
lua_Integer e = aux_getn(L, 1, TAB_RW) + 1; /* first empty element */
表中的第一個空元素的位置索引(也就是最後一個位置+1).
接着看一下在函數 tremove
中的判斷條件: 1 <= pos && pos <= size + 1
, 其中的 size
也在函數最開始有定義, 跟函數 tinsert
中的 e
徹底同樣:
lua_Integer size = aux_getn(L, 1, TAB_RW);
相關的幾個定義:
27 #define TAB_R 1 /* read */ 28 #define TAB_W 2 /* write */ 29 #define TAB_L 4 /* length */ 30 #define TAB_RW (TAB_R | TAB_W) /* read/write */ 31 32 33 #define aux_getn(L,n,w) (checktab(L, n, (w) | TAB_L), luaL_len(L, n)) 34 35 36 static int checkfield (lua_State *L, const char *key, int n) { 37 lua_pushstring(L, key); 38 return (lua_rawget(L, -n) != LUA_TNIL); 39 } 40 41 42 /* 43 ** Check that 'arg' either is a table or can behave like one (that is, 44 ** has a metatable with the required metamethods) 45 */ 46 static void checktab (lua_State *L, int arg, int what) { 47 if (lua_type(L, arg) != LUA_TTABLE) { /* is it not a table? */ 48 int n = 1; /* number of elements to pop */ 49 if (lua_getmetatable(L, arg) && /* must have metatable */ 50 (!(what & TAB_R) || checkfield(L, "__index", ++n)) && 51 (!(what & TAB_W) || checkfield(L, "__newindex", ++n)) && 52 (!(what & TAB_L) || checkfield(L, "__len", ++n))) { 53 lua_pop(L, n); /* pop metatable and tested metamethods */ 54 } 55 else 56 luaL_argerror(L, arg, "table expected"); /* force an error */ 57 } 58 }
如今咱們明白這個判斷條件的意思了, 就是對第二個參數(插入位置索引/刪除位置索引)進行判斷, 若是它超出當前表的大小, 那麼就返回錯誤.
這種表現明顯跟咱們之前版本的 lua
不同, 之前(5.1)能夠任意取一個位置索引進行插入, 好比這樣:
pi@rpi /opt/software $ lua5.1 Lua 5.1.5 Copyright (C) 1994-2012 Lua.org, PUC-Rio > touches = {} > touch={id=100} > table.insert(touches, 1000000, touch) >
那麼咱們看看 5.1
中這兩個函數(tinsert/tremove)的源代碼:
90 static int tinsert (lua_State *L) { 91 int e = aux_getn(L, 1) + 1; /* first empty element */ 92 int pos; /* where to insert new element */ 93 switch (lua_gettop(L)) { 94 case 2: { /* called with only 2 arguments */ 95 pos = e; /* insert new element at the end */ 96 break; 97 } 98 case 3: { 99 int i; 100 pos = luaL_checkint(L, 2); /* 2nd argument is the position */ 101 if (pos > e) e = pos; /* `grow' array if necessary */ 102 for (i = e; i > pos; i--) { /* move up elements */ 103 lua_rawgeti(L, 1, i-1); 104 lua_rawseti(L, 1, i); /* t[i] = t[i-1] */ 105 } 106 break; 107 } 108 default: { 109 return luaL_error(L, "wrong number of arguments to " LUA_QL("insert")); 110 } 111 } 112 luaL_setn(L, 1, e); /* new size */ 113 lua_rawseti(L, 1, pos); /* t[pos] = v */ 114 return 0; 115 } 116 117 118 static int tremove (lua_State *L) { 119 int e = aux_getn(L, 1); 120 int pos = luaL_optint(L, 2, e); 121 if (!(1 <= pos && pos <= e)) /* position is outside bounds? */ 122 return 0; /* nothing to remove */ 123 luaL_setn(L, 1, e - 1); /* t.n = n-1 */ 124 lua_rawgeti(L, 1, pos); /* result = t[pos] */ 125 for ( ;pos<e; pos++) { 126 lua_rawgeti(L, 1, pos+1); 127 lua_rawseti(L, 1, pos); /* t[pos] = t[pos+1] */ 128 } 129 lua_pushnil(L); 130 lua_rawseti(L, 1, e); /* t[e] = nil */ 131 return 1; 132 }
很顯然, 在 5.1
中對位置索引的判斷處理不太同樣:
if (pos > e) e = pos; /* `grow' array if necessary */
若是索引位置大於當前最大位置, 則把索引位置賦給當前最大位置, 至關於擴大了表, 這是一個能夠動態"生長"的數組, 這樣的話可能須要分配更多的無用空間. 也許出於優化考慮, 在 5.3
中不容許這麼作了. 因此就讓咱們之前正常的代碼出錯了.
若是想了解更清楚, 能夠在源代碼裏搜索一下函數(或者宏) luaL_argcheck
:
pi@rpi /opt/software/lua-5.3.2 $ git grep -n "luaL_argcheck" ... src/lauxlib.h:114:#define luaL_argcheck(L, cond,arg,extramsg) \ ...
看樣子是個宏, 打開 src/lauxlib.h
, 查到以下宏定義:
114 #define luaL_argcheck(L, cond,arg,extramsg) \ 115 ((void)((cond) || luaL_argerror(L, (arg), (extramsg))))
發現又調用了一個 luaL_argerror
, 先在本文件裏查一下, 發現有函數聲明:
38 LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg);
那麼函數定義應該在 src/lauxlib.c
中, 再用 git grep -n
搜一把, 以下:
pi@rpi /opt/software/lua-5.3.2 $ git grep -n "luaL_argerror" ... src/lauxlib.c:164:LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) { ...
很好, 打開看看具體代碼:
164 LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) { 165 lua_Debug ar; 166 if (!lua_getstack(L, 0, &ar)) /* no stack frame? */ 167 return luaL_error(L, "bad argument #%d (%s)", arg, extramsg); 168 lua_getinfo(L, "n", &ar); 169 if (strcmp(ar.namewhat, "method") == 0) { 170 arg--; /* do not count 'self' */ 171 if (arg == 0) /* error is in the self argument itself? */ 172 return luaL_error(L, "calling '%s' on bad self (%s)", 173 ar.name, extramsg); 174 } 175 if (ar.name == NULL) 176 ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : "?"; 177 return luaL_error(L, "bad argument #%d to '%s' (%s)", 178 arg, ar.name, extramsg); 179 }
看得出來, 咱們的試驗代碼觸發了最後一條判斷語句:
... 175 if (ar.name == NULL) 176 ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : "?"; 177 return luaL_error(L, "bad argument #%d to '%s' (%s)", 178 arg, ar.name, extramsg); ...
--結束