Elixir NIF 中的正确资源处理

Posted

技术标签:

【中文标题】Elixir NIF 中的正确资源处理【英文标题】:Proper resource handling in Elixir NIF 【发布时间】:2018-08-19 11:39:13 【问题描述】:

我正在尝试为简单的线性代数实现 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 两次会导致段错误:

iex(3)> LinearAlgebra.rows(LinearAlgebra.matrix(2,3))
1543520864
iex(4)> LinearAlgebra.rows(LinearAlgebra.matrix(2,3))
zsh:  (core dumped)  iex -S mix

(注意“相同”矩阵的不同结果)。

我在关注 Andrea Leopardi 的 tutorial 时进行了轻微的(我不确定是不是这样)更改以对抗 gcc 警告。恕我直言,最重要的是这部分

  la_matrix *mat_ptr;
  if(!enif_get_resource(env, argv[0], LA_MATRIX_TYPE, (void **) &mat_ptr))
    return enif_make_atom(env, "err");

而 Andrea Leopardi 使用

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);

iex 调用LinearAlgebra.rows 会导致段错误。

谁能告诉我在 NIF 中处理结构的正确方法?

附:对不起 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);

但是,我会等待一些更优雅的解决方案,并且到目前为止不会接受这个答案。

【讨论】:

以上是关于Elixir NIF 中的正确资源处理的主要内容,如果未能解决你的问题,请参考以下文章

正确处理 asp.net core 中的资源

正确处理 ASP.net MVC 4 Web Api 路由中的嵌套资源

Laravel_Elixir_gulp任务利器安装

Elixir CRC 无法在 MAC 操作系统上编译

用户应该是 Laravel 中的资源吗?

正确处理 SmtpClient 使用的资源