Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lua C API: Initializing a variable matrix in a structure C

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?

like image 321
Azoun Avatar asked Jan 03 '23 23:01

Azoun


2 Answers

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 the return), add lua_pushinteger( L, nbyte ); and change the return 1; to return 2;. (This is just convenience so we don't have to re-compute the size.) Further, add

static 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_NUMBERs… 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} to LuaBasic_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), not data+((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.)

like image 70
nobody Avatar answered Jan 18 '23 23:01

nobody


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].

like image 44
lhf Avatar answered Jan 18 '23 22:01

lhf