I'm trying to create an userdata with Lua C API with a metatable associated where I will collect a matrix.
What I can't get is how to set every component of the initialized matrix to zero.
I compile my Lua module C code as I described here.
The C code I have is the following:
#include "lauxlib.h"
#include "lua.h"
typedef struct {
LUA_NUMBER data[1][1];
int row;
int col;
}matrix;
// Create a matrix full of zeros
static int lb_newmatrix(lua_State *L)
{
// Variable declarations
int i,j;
matrix *temp;
// Input checks
if (lua_gettop(L)!=2)
{
lua_pushstring(L,"\n Two input required");
lua_error(L);
}
//--> Check I° index m riga
luaL_checktype(L,1,LUA_TNUMBER);
if (lua_tonumber(L,1)<0)
{
lua_pushstring(L,"\nRow number must be positive");
lua_error(L);
}
//--> Check II° index n colonna
luaL_checktype(L,2,LUA_TNUMBER);
if (lua_tonumber(L,2)<0)
{
lua_pushstring(L,"\nColumn number must be positive");
lua_error(L);
}
// Computation of memory allocation
int m = lua_tonumber(L,1);
int n = lua_tonumber(L,2);
size_t nbyte = 2*sizeof(int)+sizeof(LUA_NUMBER)*m*n;
size_t nbyte2 = sizeof(matrix)+sizeof(LUA_NUMBER)*(m*n-1);
// Memory allocation
temp = (matrix *)lua_newuserdata(L,nbyte);
// Matrix dimension setting
temp->row = m;
temp->col = n;
// Matrix inizialization
/* PROBLEM HERE */
for (i=1;i==m;i++)
{
for(j=1;j==n;j++)
{
temp->data[i][j] = 0;
}
}
//-------------------------------
// If I de-comment these line,
// the matrix is written but
// element with equal sum indices
// rewrite!!!
//-------------------------------
// temp->data[1][1] = nbyte;
// temp->data[1][2] = nbyte2;
// temp->data[1][3] = 13;
// temp->data[2][1] = nbyte2;
// temp->data[2][2] = 22;
// temp->data[2][3] = 23;
// temp->data[3][1] = 31;
// temp->data[3][2] = 32;
// temp->data[3][3] = 33;
// Link the userdata to the metatable "basic"
luaL_getmetatable(L,"basic");
lua_setmetatable(L,-2);
return 1;
}
static int lb_index(lua_State *L)
{
/* Check input Numbers */
if (lua_gettop(L)>3)
{
lua_pushstring(L,"\nOnly two inputs are needed:\n1) Point\n2) N° row\n3) N° col");
lua_error(L);
}
/* Check if the first input is userdata basic */
matrix *temp = (matrix *)luaL_checkudata(L,1,"basic");
/* I° index check ROW */
luaL_checktype(L,2,LUA_TNUMBER);
if (lua_tointeger(L,2)<0||lua_tointeger(L,2)>temp->row)
{
lua_pushstring(L,"\n First index should be 1 to n");
lua_error(L);
}
/* II° index check COLUMN */
luaL_checktype(L,3,LUA_TNUMBER);
if (lua_tointeger(L,3)<0||lua_tointeger(L,3)>temp->col)
{
lua_pushstring(L,"\n Second index should be 1 to m");
lua_error(L);
}
int row = lua_tointeger(L,2);
int col = lua_tointeger(L,3);
/* Insert the index value of userdata on top of the stack */
lua_pushnumber(L,temp->data[row][col]);
return 1;
}
/**********************
* MODULE DECLARATION *
**********************/
static const struct luaL_Reg LuaBasic_f [] = {//
{"NewMatrix",lb_newmatrix},
{ "__index", lb_index},
{ NULL, NULL}};
static const struct luaL_Reg LuaBasic_m [] = {//
{ NULL, NULL}};
LUA_API int luaopen_LuaBasic(lua_State *L)
{
/* Insert basic metatable "basic" into the stack */
luaL_newmetatable(L,"basic");
/* Copy the "basic" metatable
and push it into the stack */
lua_pushvalue(L,-1);
/* basic["__index"] = basic */
lua_setfield(L,-2,"__index");
/* register all the function
into LuaBasic_m into the
basic table metatable */
luaL_setfuncs(L,LuaBasic_m,0);
luaL_newlib(L,LuaBasic_f);
return 1;
}
The related Lua Code is the following:
lb = require("LuaBasic")
A = lb.NewMatrix(3,3)
print(A)
print("--------------")
print(lb.__index(A,1,1))
print(lb.__index(A,1,2))
print(lb.__index(A,1,3))
print(lb.__index(A,2,1))
print(lb.__index(A,2,2))
print(lb.__index(A,2,3))
print(lb.__index(A,3,1))
print(lb.__index(A,3,2))
print(lb.__index(A,3,3))
print("--------------")
print("row = "..lb.GetRow(A))
print("col = "..lb.GetCol(A))
and the output I get is:
userdata: 007C2940
--------------
1.#QNAN
1.#QNAN
1.#QNAN
1.#QNAN
1.#QNAN
1.#QNAN
1.#QNAN
1.#QNAN
1.#QNAN
--------------
row = 3
col = 3
I don't understand why I can't write the zero values into initialized matrix.
What do I do wrong?
Well, how could you find out? You could start a debugger and look at the data in memory. Or… this being Lua, you could extend your library to make that easy. So, if you want to follow along… (otherwise just skip the quote blocks)
At the end of
lb_newmatrix
(just before thereturn
), addlua_pushinteger( L, nbyte );
and change thereturn 1;
toreturn 2;
. (This is just convenience so we don't have to re-compute the size.) Further, addstatic int lb_peek( lua_State *L ) { int nbytes = luaL_checkinteger( L, 2 ); char *data = (char*)luaL_checkudata(L,1,"basic"); lua_pushlstring( L, data, nbytes ); return 1; }
and add that function to
LuaBasic_m
as{"peek",lb_peek}
. Recompile, fire up the Lua interpreter and load the library.> m, size = lb.NewMatrix( 3, 3 ) -- make a small matrix > data = m:peek( size ) -- get the memory as a string > (("n "):rep(3*3).."I I"):unpack( data ) -- and unpack the values 0.0 6.366e-314 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0 0 81 -- (note: the trailing '81' is the offset of the next character in -- the string beyond what was read and not part of your data) -- so your matrix looks like: -- 0.0 6.366e-314 0.0 -- 0.0 0.0 0.0 -- 0.0 0.0 0.0 -- and has 0 rows and 0 columns (according to the data…)
This doesn't look right… That
6.366e-314
shouldn't be there. Let's look at what this looks like as integers…> size/4 20 > (("I4"):rep(20)):unpack( data ) 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 81
A-ha… your matrix dimensions are in the wrong place! (See the 3, 3 in the middle of the data area?)
You're telling the compiler that there's a 1x1 array of LUA_NUMBER
s… but then proceed to make it larger. The compiler emits code that assumes that, given a matrix *m
, m->row
is at *(m+sizeof(LUA_NUMBER[1][1]))
but that's where you write your data…
So you need to change the order of your struct
's fields: Keep variable-sized parts last!
typedef struct {
int row;
int col;
LUA_NUMBER data[1][1];
} matrix;
After changing, recompiling & restarting Lua, we can check:
> m, size = lb.NewMatrix( 3, 3 ) > data = m:peek( size ) > ("I I"..("n "):rep(3*3)):unpack( data ) 3 3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 81
which is a proper all-zero 3x3 matrix. Now let's check the data placement. Because we don't want to re-compile all the time to test new assignments, add
/* m[i][j] = v */ static int lb_set( lua_State *L ) { matrix *m = (matrix*)luaL_checkudata(L,1,"basic"); int i = luaL_checkinteger( L, 2 ); int j = luaL_checkinteger( L, 3 ); lua_Number v = luaL_checknumber( L, 4 ); m->data[i][j] = v; return 0; }
and add
{"set",lb_set}
toLuaBasic_m
. Recompile, reload:> m, size = lb.NewMatrix( 3, 3 ) > for i = 0, 2 do for j = 0, 2 do >> m:set( i, j, (i+1) + (j+1)/10 ) >> print( ("I I"..("n "):rep(3*3)):unpack( m:peek( size ) ) ) >> end end 3 3 1.1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 81 3 3 1.1 1.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 81 3 3 1.1 1.2 1.3 0.0 0.0 0.0 0.0 0.0 0.0 81 3 3 1.1 2.1 1.3 0.0 0.0 0.0 0.0 0.0 0.0 81 3 3 1.1 2.1 2.2 0.0 0.0 0.0 0.0 0.0 0.0 81 3 3 1.1 2.1 2.2 2.3 0.0 0.0 0.0 0.0 0.0 81 3 3 1.1 2.1 3.1 2.3 0.0 0.0 0.0 0.0 0.0 81 3 3 1.1 2.1 3.1 3.2 0.0 0.0 0.0 0.0 0.0 81 3 3 1.1 2.1 3.1 3.2 3.3 0.0 0.0 0.0 0.0 81
hmm… see any pattern? :-) The data is written at
data+((
1
*i)+j)*sizeof(lua_Number)
, notdata+((
3
*i)+j)*sizeof(lua_Number)
(or whatever the size of your matrix).
Again, you're telling the compiler that you have a 1x1 array. (The compiler doesn't care that you're doing out-of bounds accesses, in fact you could write i[m->data][j]
instead of m->data[i][j]
and you'd get exactly the same behavior.) So you cannot let the compiler do the offset computations for you, you'll have to do that manually. Declaring a two-dimensional array just gets in the way, so change your struct
once more to
typedef struct {
int row;
int col;
LUA_NUMBER data[0];
} matrix;
(The [0]
is just a convention for a trailing variable-sized part. You could say data[1]
, but that might make sense as a stand-alone struct
. data[0]
is nothing at all, which doesn't make sense as a fixed-sized struct
– so it communicates relatively clearly (if you know about this convention) that this is supposed to be variable-sized.)
Then change all occurrences of m->data[i][j]
to m->data[m->col*i + j]
. (Just try to compile and the compiler will give errors for the lines you need to adjust.)
A final test:
> m, size = lb.NewMatrix( 3, 3 ) > for i = 0, 2 do for j = 0, 2 do >> m:set( i, j, (i+1) + (j+1)/10 ) >> print( ("I I"..("n "):rep(3*3)):unpack( m:peek( size ) ) ) >> end end 3 3 1.1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 81 3 3 1.1 1.2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 81 3 3 1.1 1.2 1.3 0.0 0.0 0.0 0.0 0.0 0.0 81 3 3 1.1 1.2 1.3 2.1 0.0 0.0 0.0 0.0 0.0 81 3 3 1.1 1.2 1.3 2.1 2.2 0.0 0.0 0.0 0.0 81 3 3 1.1 1.2 1.3 2.1 2.2 2.3 0.0 0.0 0.0 81 3 3 1.1 1.2 1.3 2.1 2.2 2.3 3.1 0.0 0.0 81 3 3 1.1 1.2 1.3 2.1 2.2 2.3 3.1 3.2 0.0 81 3 3 1.1 1.2 1.3 2.1 2.2 2.3 3.1 3.2 3.3 81
So the code works now.
The final problem with your test assignments is that you're accessing [1]
, [2]
, [3]
instead of [0]
, [1]
, [2]
. C is all about offsets and uses zero-based indexing. The first element is at [0]
not [1]
and the last element is at [size-1]
instead of [size]
. While you could allocate a (n+1)x(m+1) matrix and could then use 1…n and 1…m, that would waste space (not much for 3x3, but more and more if the matrices get bigger). So it's probably better to adjust your code to the C convention.
You will also have to modify your Lua-visible access functions, they currently allow out-of-bounds access. (data[3][3]
is outside of a 3x3 matrix, the "outermost" field is data[2][2]
or data[m-1][n-1]
.) You'll have to decide whether you want to stick to the Lua/index-based convention (1-based, index goes from 1 to n) or to the C/offset-based convention (0-based, offset from 0 to (n-1)). (If this is to be used for matrix math, it may be better to pick 0-based indexing/offsetting, as some algorithms and formulas might assume this and you probably don't want to change them all.)
The problem is not directly related to Lua; it's mainly a C problem. In short, you cannot write temp->data[i][j]
when temp->data
is dynamically allocated and hope that it works because the C compiler knows that temp->data
is a 1x1 matrix.
To fix this, first define
typedef struct {
int row;
int col;
LUA_NUMBER data[1];
}matrix;
It is crucial that data
is the last field.
Then address position i,j in the matrix linearly as data[i*col + j]
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With