C语言 安全地释放 XS 代码中的资源(在作用域退出时运行析构函数)



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

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

伪代码来说明我的担忧:

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 代码返回到 Perl 代码时?

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

我可以创建一个在
  • 析构函数中运行必要清理的类,然后创建一个包含此类实例的凡人 SV。在未来的某个时候,Perl 将释放该 SV 并运行我的析构函数。然而,这似乎相当倒退,必须有更好的方法。

  • XSAWYERX的XS Fun小册子似乎详细讨论了DESTROY方法,但没有处理源自XS代码异常。

  • LEONT 的Scope::OnExit模块具有使用SAVEDESTRUCTOR()SAVEDESTRUCTOR_X()宏的 XS 代码。这些似乎没有记录在案。

  • Perl API 将save_destructor()save_destructor_x()函数列为公共函数,但未记录。

  • Perl 的scope.h标头(包含在perl.h中(声明了宏SAVEDESTRUCTOR(f,p)SAVEDESTRUCTOR_X(f,p)宏,没有任何进一步的解释。从上下文和Scope::OnExit代码来看,f是一个函数指针,p是一个将传递给f的 void 指针。_X 版本适用于使用pTHX_宏参数声明的函数。

我在这方面走在正确的轨道上吗?我应该适当地使用这些宏吗?它们是在哪个 Perl 版本中引入的?是否有关于其使用的进一步指导?析构函数究竟何时触发?大概是在与FREETMPSLEAVE宏相关的点上?

经过进一步的研究,事实证明SAVEDESTRUCTOR实际上是有记录的——用perlguts而不是perlapi。那里记录了确切的语义。

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

摘自 本地化perlguts的变化,其中讨论了等效于{ local $foo; ... }块:

有一种方法可以通过Perl API从C完成类似的任务:创建一个伪块,并安排在结束时自动撤消一些更改,无论是显式的,还是通过非本地出口(通过die(((。块状构造由一对ENTER/LEAVE宏创建(请参阅 在 perlcall 中返回标量(。这样的结构可以专门为一些重要的本地化任务创建,也可以使用现有的结构(如封闭Perl子例程/块的边界,或用于释放TMP的现有对(。(在第二种情况下,额外本地化的开销几乎可以忽略不计。请注意,任何 XSUB 都自动包含在ENTER/LEAVE对中。

在这样的伪块中,可以使用以下服务:

  • [...]

  • SAVEDESTRUCTOR(DESTRUCTORFUNC_NOCONTEXT_t f, void *p)

    伪块结束时,调用函数f,唯一的参数p

  • SAVEDESTRUCTOR_X(DESTRUCTORFUNC_t f, void *p)

    伪块结束时,使用隐式上下文参数(如果有的话(调用函数f,并p.

本节还列出了几个专门的析构函数,如SAVEFREESV(SV *sv)SAVEMORTALIZESV(SV *sv),在某些情况下可能比过早sv_2mortal()更正确。

这些宏基本上已经永远可用了,至少是 Perl 5.6 或更早版本。

相关内容

  • 没有找到相关文章

最新更新