安全地释放 XS 代码中的资源(范围退出时 运行 个析构函数)

Safely freeing resources in XS code (running destructors on scope exit)

我正在编写一个 XS 模块。我分配一些资源(例如 malloc()SvREFCNT_inc()),然后执行一些涉及 Perl API 的操作,然后释放资源。这在普通 C 中很好,因为 C 没有异常,但是使用 Perl API 的代码可能 croak(),从而阻止正常清理和泄漏资源。因此,除了相当简单的情况外,似乎不可能编写正确的 XS 代码。

当我 croak() 自己时,我可以清理到目前为止分配的所有资源,但我可能会直接调用 croak() 的函数,这会避开我编写的任何清理代码。

Pseudo-code 说明我的担忧:

static void some_other_function(pTHX_ Data* d) {
  ...
  if (perhaps) croak("Could not frobnicate the data");
}

MODULE = Example  PACKAGE = Example

void
xs(UV n)
  CODE:
  {
    /* Allocate resources needed for this function */
    Data* object_graph;
    Newx(object_graph, 1, Data);
    Data_init(object_graph, n);

    /* Call functions which use the Perl API */
    some_other_function(aTHX_ object_graph);

    /* Clean up before returning.
     * Not run if above code croak()s!
     * Can this be put into the XS equivalent of a  "try...finally" block?
     */
    Data_destroy(object_graph);
    Safefree(object_graph);
  }

那么如何安全地清理 XS 代码中的资源?当抛出异常时,或者当我从 XS 代码 return 返回到 Perl 代码时,我如何注册一些 运行 的析构函数?

到目前为止我的想法和发现:

我的方向正确吗?我应该适当地使用这些宏吗?它们是在哪个 Perl 版本中引入的?是否有关于它们的使用的进一步指导?什么时候触发析构函数?大概是在与 FREETMPSLEAVE 宏有关的地方?

经过进一步研究,事实证明 SAVEDESTRUCTOR 实际上已被记录在案 – 在 perlguts 而不是 perlapi 中。那里记录了确切的语义。

因此我假设 SAVEDESTRUCTOR 应该用作清理的 "finally" 块,并且足够安全和​​稳定。

摘自 Localizing changes in perlguts,其中讨论了 { local $foo; ... } 块的等效内容:

There is a way to achieve a similar task from C via Perl API: create a pseudo-block, and arrange for some changes to be automatically undone at the end of it, either explicit, or via a non-local exit (via die()). A block-like construct is created by a pair of ENTER/LEAVE macros (see Returning a Scalar in perlcall). Such a construct may be created specially for some important localized task, or an existing one (like boundaries of enclosing Perl subroutine/block, or an existing pair for freeing TMPs) may be used. (In the second case the overhead of additional localization must be almost negligible.) Note that any XSUB is automatically enclosed in an ENTER/LEAVE pair.

Inside such a pseudo-block the following service is available:

  • […]

  • SAVEDESTRUCTOR(DESTRUCTORFUNC_NOCONTEXT_t f, void *p)

    At the end of pseudo-block the function f is called with the only argument p.

  • SAVEDESTRUCTOR_X(DESTRUCTORFUNC_t f, void *p)

    At the end of pseudo-block the function f is called with the implicit context argument (if any), and p.

该部分还列出了一些专门的析构函数,例如 SAVEFREESV(SV *sv)SAVEMORTALIZESV(SV *sv),在某些情况下它们可能比过早的 sv_2mortal() 更正确。

这些宏基本上一直可用,至少 Perl 5.6 或更早版本。