Правильная обработка ресурсов в Elixir NIF
Я пытаюсь реализовать NIF для простой линейной алгебры. Вот моя внутренняя структура матрицы:
typedef struct la_matrix {
uint rows, columns;
double **data;
} la_matrix;
И вот "конструктор" для этого:
la_result
la_matrix_constructor(la_matrix **res,
const uint rows,
const uint columns)
{
if (rows == 0 || columns == 0)
return dimensional_problems;
// allocate memory for meta-structure
*res = malloc(sizeof(la_matrix));
if (*res == NULL)
return null_ptr;
// allocater memory for array of pointers to rows
(*res)->data = malloc(rows * sizeof(double*));
if ((*res)->data == NULL) {
free(*res);
return null_ptr;
}
//allocate memory for each row
uint i = 0;
bool failed = false;
for (; i < rows; i++) {
(*res)->data[i] = malloc(columns * sizeof(double));
if ((*res)->data[i] == NULL) {
failed = true;
break;
}
}
if (failed) {
// one step back, since i-th row wasn't allocated
i -= 1;
for(; i < ~((uint) 0); i--)
free((*res)->data[i]);
free((*res)->data);
free(*res);
return null_ptr;
}
(*res)->rows = rows;
(*res)->columns = columns;
return ok;
}
Тогда у меня есть две оболочки для NIF - одна для конструктора:
static ERL_NIF_TERM
nif_matrix_constructor(ErlNifEnv *env,
int argc,
const ERL_NIF_TERM *argv)
{
uint rows, columns;
enif_get_uint(env, argv[0], &rows);
enif_get_uint(env, argv[1], &columns);
la_matrix **mat_res = enif_alloc_resource(LA_MATRIX_TYPE, sizeof(la_matrix *));
la_matrix *mat_ptr;
la_result result = la_matrix_constructor(&mat_ptr, rows, columns);
if (result != ok)
return enif_make_atom(env, "err");
memcpy((void *) mat_res, (void *) &mat_ptr, sizeof(la_matrix *));
ERL_NIF_TERM term = enif_make_resource(env, mat_res);
enif_release_resource(mat_res);
return term;
}
И один, чтобы проверить, работает ли конструктор правильно:
static ERL_NIF_TERM
nif_matrix_rows(ErlNifEnv *env,
int argc,
const ERL_NIF_TERM *argv)
{
la_matrix *mat_ptr;
if(!enif_get_resource(env, argv[0], LA_MATRIX_TYPE, (void **) &mat_ptr))
return enif_make_atom(env, "err");
return enif_make_uint(env, mat_ptr->rows);
}
Кажется, что оболочка конструктора работает отлично (я проверял это с помощью printf
), но nif_matrix_rows
возвращает странные результаты, например
iex(1)> mat = LinearAlgebra.matrix(2,3)
""
iex(2)> LinearAlgebra.rows(mat)
1677732752
И прямо мимо LinearAlgebra.matrix(2,3)
в LinearAlgebra.rows
дважды приводит к segfault:
iex(3)> LinearAlgebra.rows(LinearAlgebra.matrix(2,3))
1543520864
iex(4)> LinearAlgebra.rows(LinearAlgebra.matrix(2,3))
zsh: (core dumped) iex -S mix
(Обратите внимание на разные результаты для "одинаковых" матриц).
Я следовал за уроком Андреа Леопарди с незначительными (я не совсем уверен, если они так) изменениями в бою gcc
предупреждения. Самым важным, ИМХО, была эта часть
la_matrix *mat_ptr;
if(!enif_get_resource(env, argv[0], LA_MATRIX_TYPE, (void **) &mat_ptr))
return enif_make_atom(env, "err");
в то время как Андреа Леопарди использует
db_conn_t **conn_res;
enif_get_resource(env, argv[0], DB_RES_TYPE, (void *) conn_res);
db_conn_t *conn = *conn_res;
Но это выглядит недействительным для меня, потому что, AFAIR, (void *) conn_res
предполагает, что conn_res
был инициализирован.
Вот ошибка, которая возникает, когда я использую способ Андреа:
src/nif.c: In function ‘nif_matrix_rows’:
src/nif.c:72:3: warning: ‘mat_res’ is used uninitialized in this function [-Wuninitialized]
enif_get_resource(env, argv[0], LA_MATRIX_TYPE, (void *) mat_res);
И зовет LinearAlgebra.rows
от iex
вызывает segfault.
Можно ли сказать мне правильный способ обработки структур в NIF?
PS Извините за код на C, я никогда не писал что-то большее, чем связка helloworlds.
1 ответ
Проблема была действительно в nif_matrix_rows
: с моим кодом Elixir передает указатель на указатель на структуру (la_matrix **
) и я предположил, что это будет правильный указатель.
Итак, быстрое решение
static ERL_NIF_TERM
nif_matrix_rows(ErlNifEnv *env,
int argc,
const ERL_NIF_TERM *argv)
{
la_matrix const **mat_res;
if(!enif_get_resource(env, argv[0], LA_MATRIX_TYPE,(void **) &mat_res))
return enif_make_atom(env, "err");
la_matrix const *mat_ptr = *mat_res;
return enif_make_uint(env, mat_ptr->rows);
}
Однако я подожду некоторое время для более элегантного решения и пока не приму этот ответ.