【发布时间】:2018-01-28 19:29:25
【问题描述】:
我正在编写一个 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模块具有 XS code 使用SAVEDESTRUCTOR()和SAVEDESTRUCTOR_X()宏。这些似乎没有记录。Perl API 列出
save_destructor()和save_destructor_x()功能为公开但未记录。Perl 的
scope.h标头(包含在perl.h中)声明了SAVEDESTRUCTOR(f,p)和SAVEDESTRUCTOR_X(f,p)宏,没有任何进一步的解释。从上下文和Scope::OnExit代码来看,f是一个函数指针,p是一个将传递给f的空指针。 _X 版本适用于使用pTHX_宏参数声明的函数。
我在正确的轨道上吗?我应该酌情使用这些宏吗?它们是在哪个 Perl 版本中引入的?关于它们的使用是否有任何进一步的指导?究竟什么时候触发析构函数?大概与FREETMPS 或LEAVE 宏有关?
【问题讨论】:
-
顺便问下好问题...
-
Perl API 通常只会在无效输入时发出嘶嘶声。如果您调用 Perl API 的方式可能会发牢骚,您只需自己检查参数的有效性即可。唯一的例外是调用 Perl 代码,您可以在其中使用
G_EVAL flag。 -
@nwellnhof,您忘记了致命警告(我在上面提到过)。检查你是否真的得到了一个数字而不是使用
SvIV是不切实际的。但这可能会警告(未初始化或非数字),这可能会死。 -
@ikegami 我正在分配执行 XS 函数期间所需的临时数据结构(缓冲区、队列、图表)。它们不会作为 SV 返回。它们不受某些常量的限制,因此不能进行堆栈分配。我添加了一段伪代码来说明我的问题的结构。
-
我不知道
SAVEDESTRUCTOR,这似乎是一种运行清理代码的防弹方法。不过,我不会使用 Perl 异常来处理我自己的内部 C 函数中的错误。相反,我更愿意返回错误代码并在释放所有资源后抛出 XSUB。这对于许多典型情况应该已经足够了,比如从AV或HV中提取数据。即使你的库的用户可以偷偷输入一些奇怪的数据,使 Perl API 函数抛出异常并导致内存泄漏,我也不会太担心,除非它绝对是关键任务代码。
标签: c perl destructor xs perl-xs