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版本中引入的?是否有关于它们使用的进一步指导?析构函数是在什么时候被触发的?大概与FREETMPS
或LEAVE
宏相关的点上?
SvIV
并不实际。但是可以发出警告(未初始化或非数字),这可能会导致死机。 - ikegamiSAVEDESTRUCTOR
,但它似乎是一种可靠的运行清理代码的方式。然而,在我的内部C函数中,我不会使用Perl异常来处理错误。相反,我更喜欢返回错误代码,并在释放所有资源后使XSUB抛出异常。这对于许多典型情况应该已经足够了,例如从AV
或HV
中提取数据。即使有一种方法可以让您库的用户插入一些奇怪的数据,使得Perl API函数抛出异常并导致内存泄漏,除非这是绝对关键的代码,否则我不会太担心。 - nwellnhof