我应该如何释放类型映射中为 argout 结构数组分配的内存?

Posted

技术标签:

【中文标题】我应该如何释放类型映射中为 argout 结构数组分配的内存?【英文标题】:How should I free memory allocated in a typemap for an argout array of structures? 【发布时间】:2017-06-28 16:36:51 【问题描述】:

我正在使用 SWIG 为 Python 包装一个 C 库。其中一种 C 方法采用 指向结构缓冲区的指针以及要填充和填充的元素数量 指向的结构。对于 Python API,我只想提供 元素的数量和返回值是填充结构的元组。

  C     : int fill_widgets(widget_t *buffer, int num_widgets);
  Python: fill_widgets(num_widgets) -> (widget, widget,...)

我已经编写了可以按我想要的方式工作的类型图 - 类型图分配 基于 Python 输入参数的缓冲区,将缓冲区传递给 C 方法, 将其转换为 Python 元组,然后返回小部件的元组。但我想不通 如果/何时/如何我需要释放在我的类型映射中分配的内存。

我最初包含一个 freearg 类型映射来在包装器时释放缓冲区 函数退出,但我相信返回给 Python 的结构仍在使用 物理内存(即没有复制内存,我只是得到一个代理指针 即使用相同的缓冲区)。我也尝试设置 SWIG_POINTER_OWN 标志 创建代理对象时(通过 SWIG_NewPointerObj),但因为我正在创建 指向缓冲区中每个元素的代理指针,释放它们没有意义 全部。在这两种情况下,Python 最终都会在稍后出现段错误 free() 调用。

因此,在创建代理时,无需在类型映射中使用 freearg 或 SWIG_POINTER_OWN, 当 Python tuple-of-structs 超出范围时如何释放内存?

这是一个简单的 SWIG 界面,演示了我所拥有的:

%module "test"

%typemap (in, numinputs=1) (BUF, NUM)
    $2 = PyInt_AsLong($input);
    $1 = ($1_type)calloc($2, sizeof($*1_type));


%typemap (argout) (BUF, NUM)
    PyObject *tpl = PyTuple_New($2);
    for ($2_ltype i=0; i<$2; i++)
    
        PyTuple_SET_ITEM(tpl, i, SWIG_NewPointerObj(&$1[i], $1_descriptor, 0));
    
    $result = SWIG_Python_AppendOutput($result, tpl);


%typemap (freearg) (BUF, NUM)
    //free($1);


%apply (BUF, NUM) (widget_t *buf, int num_widgets);

%inline 
typedef struct int a; int b; widget_t;

int fill_widgets(widget_t *buf, int num_widgets)

    for(int i=0; i<num_widgets; i++)
    
        buf[i].a = i;
        buf[i].b = 2*i;
    
    return num_widgets;


以及构建/运行的示例:

$ swig -python test.i
$ gcc -I/path/to/python2.7 -shared -lpython2.7 test_wrap.c  -o _test.so
$ python
>>> import test
>>> _,widgets = test.fill_widgets(4)
>>> for w in widgets: print w.a, w.b
... 
0 0
1 2
2 4
3 6
>>> 

C 中 fill_widgets 的示例用法:

int main()

    widget_t widgets[10];  // or widget_t *widgets = calloc(10, sizeof(widget_t))
    fill_widgets(widgets, 10);

【问题讨论】:

【参考方案1】:

有趣的是,您有 1 个缓冲区,但创建了 N 个 Python 代理对象,所有这些对象都存在于单个缓冲区中。

假设您不愿意从该缓冲区中复制对象,因此您将 1:1 分配给 Python 代理对象映射,然后处理原始缓冲区,我们基本上有一个解决方案可以解决。这里的目的是确保每个 Python 对象也持有对拥有内存的对象的引用。这样做我们可以保持较高的引用计数,并且只有在确定没有人可能仍然指向它时才释放内存。

最简单的解决方案是为缓冲区中的第一个对象设置 SWIG_POINTER_OWN(即指针真正引用您从 calloc 返回的内存),然后让所有其他代理对象不拥有该内存,但保留对那个的引用。

为了实现这一点,我们对您的 argout 类型映射进行了两项更改。首先,我们只为元组的第一个元素设置 SWIG_POINTER_OWN。其次,我们调用PyObject_SetAttrString 为除第一个项目之外的所有项目保留参考。所以它最终看起来像这样:

%typemap (argout) (BUF, NUM)
    PyObject *tpl = PyTuple_New($2);
    for ($2_ltype i=0; i<$2; i++)
    
        PyObject *item = SWIG_NewPointerObj(&$1[i], $1_descriptor, 0==i?SWIG_POINTER_OWN:0);
        if (i) 
            PyObject_SetAttrString(item,"_buffer",PyTuple_GET_ITEM(tpl, 0));
         
        PyTuple_SET_ITEM(tpl, i, item);
    
    $result = SWIG_Python_AppendOutput($result, tpl);

我们可以交互式地检查引用计数是否符合预期:

In [1]: import test

In [2]: import sys

In [3]: a,b=test.fill_widgets(20)

In [4]: sys.getrefcount(b[0])
Out[4]: 21

In [5]: sys.getrefcount(b[1])
Out[5]: 2

In [6]: b[1]._buffer
Out[6]: <test.widget_t; proxy of <Swig Object of type 'widget_t *' at 0xb2118d10> >

In [7]: b[1]._buffer == b[0]
Out[7]: True

In [8]: x,y,z = b[0:3]

In [9]: del a

In [10]: del b

In [11]: sys.getrefcount(x)
Out[11]: 4

In [12]: sys.getrefcount(y)
Out[12]: 2

In [13]: sys.getrefcount(z)
Out[13]: 2

In [14]: del x

In [15]: sys.getrefcount(y._buffer)
Out[15]: 3

【讨论】:

优秀的答案!我曾考虑为第一个对象设置 SWIG_POINTER_OWN,但不知道如何避免在所有对象被取消作用域之前释放 - 我没有想过保留对第一个对象的引用。 “假设你不愿意复制对象......”我什至没有想到!我想我只会 malloc 和 memcpy 循环内的每个元素,并为每个元素设置 SWIG_POINTER_OWN。然后释放原始缓冲区以干净退出。

以上是关于我应该如何释放类型映射中为 argout 结构数组分配的内存?的主要内容,如果未能解决你的问题,请参考以下文章

如果分配该数组引发异常,你应该释放一个数组吗?

怎么用new定义一个类数组,并且释放内存

如何在类型定义中为 GraphQL 字段定义数组数据类型

如何释放动态结构数组的内存

如何在C中释放指向结构的指针数组?

如何在 Swift 中为 Int 数组(自定义字符串结构)实现 Hashable 协议