python - SWIG 中的垃圾收集和自定义 getter
问题描述
我不是作者,但我使用的一个公共软件包似乎正在泄漏内存(Github issue)。我试图弄清楚如何修补它以使其正常工作。
为了缩小问题范围,有一个结构体,调用它xxx_t
。First%extend
用于使结构的成员在 Python 中可用:
%extend xxx_t {
char *surface;
}
然后是一个自定义的 getter。它在这里究竟做了什么并不重要,只是它new
用来创建一个char*
.
%{
char* xxx_t_surface_get(xxx *n) {
char *s = new char [n->length + 1];
memcpy (s, n->surface, n->length);
s[n->length] = '\0';
return s;
}
%}
目前代码有这一行来处理垃圾收集:
%newobject surface;
这似乎没有按预期工作。%newobject xxx_t::surface;
也不起作用。如果我用它替换它%newobject xxx_t_surface_get;
不起作用,因为 getter 函数被转义(在内部%{ ... %}
)。
告诉 SWIGchar*
以便释放它的正确方法是什么?
解决方案
在开始之前,值得指出一件事:因为您返回char*
它最终会使用 SWIG 的普通字符串类型映射来生成 Python 字符串。
话虽如此,让我们了解当前生成的代码是什么样的。我们可以从以下 SWIG 接口定义开始我们的调查以进行试验:
%module test
%inline %{
struct foobar {
};
%}
%extend foobar {
char *surface;
}
如果我们通过 SWIG 运行类似的东西,我们将看到一个生成的函数来包装您的_surface_get
代码,如下所示:
SWIGINTERN PyObject *_wrap_foobar_surface_get(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
PyObject *resultobj = 0;
foobar *arg1 = (foobar *) 0 ;
void *argp1 = 0 ;
int res1 = 0 ;
PyObject * obj0 = 0 ;
char *result = 0 ;
if (!PyArg_ParseTuple(args,(char *)"O:foobar_surface_get",&obj0)) SWIG_fail;
res1 = SWIG_ConvertPtr(obj0, &argp1,SWIGTYPE_p_foobar, 0 | 0 );
if (!SWIG_IsOK(res1)) {
SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "foobar_surface_get" "', argument " "1"" of type '" "foobar *""'");
}
arg1 = reinterpret_cast< foobar * >(argp1);
result = (char *)foobar_surface_get(arg1);
resultobj = SWIG_FromCharPtr((const char *)result);
/* result is never used again from here onwards */
return resultobj;
fail:
return NULL;
}
然而,这里要注意的是,当这个包装器返回时,调用你的 getter 的结果会丢失。也就是说,它甚至与返回的 Python 字符串对象的生命周期无关。
所以有几种方法可以解决这个问题:
- 一种选择是确保生成的包装器
delete[]
在调用 getter 的结果SWIG_FromCharPtr
发生后调用。这正是%newobject
在这种情况下所做的。(见下文)。 - 另一种选择是在调用之间保留分配的缓冲区,可能在某些线程本地存储中并跟踪大小以最小化分配
- 或者,我们可以使用某种基于 RAII 的对象来拥有临时缓冲区并确保它被删除。(如果我们愿意,我们可以做一些整洁的事情
operator void*
)。
如果我们改变我们的界面添加%newobject
如下:
%module test
%inline %{
struct foobar {
};
%}
%newobject surface;
%extend foobar {
char *surface;
}
然后我们看到我们生成的代码现在看起来像这样:
// ....
result = (char *)foobar_surface_get(arg1);
resultobj = SWIG_FromCharPtr((const char *)result);
delete[] result;
我们也可以在来自 github 的真实代码中看到这一点,所以这不是您要寻找的错误。
通常对于 C++,我倾向于 RAII 选项。碰巧,从 SWIG 的角度和 C++ 的角度来看,有一种巧妙的方法可以做到这一点:std::string
. 因此,我们可以通过执行以下操作以简单干净的方式修复您的泄漏:
%include <std_string.i> /* If you don't already have this... */
%extend xxx_t {
std::string surface;
}
%{
std::string xxx_t_surface_get(xxx *n) {
return std::string(n->surface, n->length);
}
%}
(不过,您也需要更改 setter 以匹配,除非您将其设为 const 所以没有 setter)
不过,关于这一点的事情是它仍然为相同的输出进行两组分配。首先,std::string
对象进行一次分配,然后对 Python 字符串对象进行分配。这就是缓冲区已经存在于 C++ 中的所有内容。因此,虽然这种更改足以解决泄漏问题并且是正确的,但您还可以进一步编写一个减少重复复制的版本:
%extend xxx_t {
PyObject *surface;
}
%{
PyObject *xxx_t_surface_get(xxx *n) {
return SWIG_FromCharPtrAndSize(n->surface, n->length);
}
%}
推荐阅读
- amazon-web-services - 无法手动删除 AWS S3 存储桶
- reactjs - 我在减速器中的语法有什么问题?
- java - 如何遍历二维数组并按行主要顺序计算每行中有多少个元素大于1
- django - 使用一个表格行生成每个项目的两个 forloop
- php - 将选择标记值传递给 laravel 框架上的路由 url
- java - 处理写得不好的外部异常的最佳方法?
- apache-spark - 从 Hive sql 中的第 n 个存储桶获取所有记录
- csrf - CSRF 令牌是 base64url 编码的吗?
- typescript - 当对象在 TypeScript 中实现某些接口时,为对象定义新的属性和方法
- php - Laravel 处理多个查询和连接的最佳实践