SWIG和C++内存泄漏与指针向量

Jas*_*son 22 c++ swig python-2.7

我使用SWIG来连接C++和Python.我创建了一个函数,它创建了一个std :: vector对象指针.在这种情况下,指向的对象并不重要.

我遇到的问题是,当object(someObject)超出Python端的范围时,它无法释放向量中对象/指针所指向的内存,从而导致内存泄漏.

当我在Python中运行它时,我收到此错误:

swig/python detected a memory leak of type 'std::vector< someObject *,std::allocator<  someObject * > > *', no destructor found.
Run Code Online (Sandbox Code Playgroud)

这个错误是因为当Python删除向量时,它只能删除向量中的指针而不是它们指向的实际指针.

如果我可以为std :: vector创建一个析构函数,这就是答案,但这是不可能的.

在任何人建议将其作为解决方案之前,我确实需要使用与对象向量相对的指针向量,特别是因为对象很大且复杂,速度是个问题.

我在Windows上使用gcc4.4,swigwin 2.0.4和Python 2.7.

Fle*_*exo 43

你看到的警告并不直接与你有一个指针向量的事实有关.请考虑以下SWIG接口文件:

%module test

// This just gets passed straight through and not used for wrapping
%{
struct foo {};
%}

struct foo;

%inline %{
  struct foo bar() { struct foo f; return f; }
%}
Run Code Online (Sandbox Code Playgroud)

使用此界面可以:

swig -Wall -python test.i && gcc -Wall -Wextra -std=c99 -shared -o _test.so test_wrap.c -I/usr/include/python2.7 && python2.7
Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> test.bar()
<Swig Object of type 'struct foo *' at 0xb7654a70>
>>> 
swig/python detected a memory leak of type 'struct foo *', no destructor found.
Run Code Online (Sandbox Code Playgroud)

问题是SWIG只看到了一个声明,而不是一个定义struct foo.默认行为是Python的代理对象免费/删除(如适用)的底层对象在这里,但它并不能够推断出如何做到这一点只基于它看到的向前声明.

如果我们扩展测试用例包含std::vector<foo>相同的观察:

%module test

%{
struct foo {};
%}

struct foo;

%include <std_vector.i>

%inline %{
  foo bar() { return foo(); }
  std::vector<foo> bar2() { 
    return std::vector<foo>(); 
  } 
%}
Run Code Online (Sandbox Code Playgroud)

这再次给出了没有析构函数的警告:

Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> print test.bar2()
<Swig Object of type 'std::vector< foo,std::allocator< foo > > *' at 0xb7671a70>swig/python detected a memory leak of type 'std::vector< foo,std::allocator< foo > > *', no destructor found.
Run Code Online (Sandbox Code Playgroud)

但是,我们可以通过确保类型的定义可用来轻松解决这个问题.因为struct foo这只是让结构的整个主体对SWIG可见.因为std::vector<T>我们需要%template用来做到这一点:

%module test

%include <std_vector.i>

%inline %{
  struct foo {};
  foo bar() { return foo(); }
  std::vector<foo> bar2() { 
    return std::vector<foo>(); 
  } 
%}

%template(FooVec) std::vector<foo>;
Run Code Online (Sandbox Code Playgroud)

现在没有警告(或泄漏):

Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> print test.bar()
<test.foo; proxy of <Swig Object of type 'foo *' at 0xb76aba70> >
>>> print test.bar2()
<test.FooVec; proxy of <Swig Object of type 'std::vector< foo > *' at 0xb76abab8> >
>>> 
Run Code Online (Sandbox Code Playgroud)

复杂的是,在您的示例中std::vector<T*>,我们可以更改我们的测试用例来说明:

%module test

%include <std_vector.i>

%inline %{
  struct foo {};
  foo bar() { return foo(); }
  std::vector<foo*> bar2() { 
    return std::vector<foo*>(1, new foo); 
  } 
%}

%template(FooVec) std::vector<foo*>;
Run Code Online (Sandbox Code Playgroud)

然后我们可以运行:

Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> print test.bar2()
<test.FooVec; proxy of <Swig Object of type 'std::vector< foo * > *' at 0xb7655a70> >
>>> 
Run Code Online (Sandbox Code Playgroud)

确实是泄漏,但关键是没有显示你注意到的警告,因为就SWIG而言,std::vector它本身已被正确删除(事实上与C++完全相同的语义).

至于如何处理泄漏,选项与C++中的通常相同.就个人而言,我会尽量避免将原始指针放在向量中,除非你真的希望指向的对象比向量更长.基本上你可以:

  1. 不在结构中存储指针
  2. 使用智能指针(std::shared_ptr或者std::unique_ptr代替增强等价物).
  3. 以某种方式手动管理内存.

我们在第二个例子中已经完成了1.SWIG 2也非常简单,3是在界面中编写和包装另一个函数的问题.

%module test

%include <std_vector.i>
%include <std_shared_ptr.i>

%{
#include <memory>
%}

%inline %{
  struct foo {};
  foo bar() { return foo(); }
  std::vector<std::shared_ptr<foo> > bar2() { 
    return std::vector<std::shared_ptr<foo> >(1, std::make_shared<foo>()); 
  } 
%}

%shared_ptr(Foo);
%template(FooVec) std::vector<std::shared_ptr<foo> >;
Run Code Online (Sandbox Code Playgroud)
Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> print test.bar2()
<test.FooVec; proxy of <Swig Object of type 'std::vector< std::shared_ptr< foo >,std::allocator< std::shared_ptr< foo > > > *' at 0xb76f4a70> >
>>> print test.bar2()[0]
<Swig Object of type 'std::vector< std::shared_ptr< foo > >::value_type *' at 0xb76f4a70>
>>> 
Run Code Online (Sandbox Code Playgroud)

哪个有效,存储共享指针并且不泄漏.

如果你真的想要做第三种方式(我会不惜一切代价避免它,因为它让你的界面对人为错误开放),使用SWIG最简单的方法是使用%extend,例如:

%module test

%include <std_vector.i>

%inline %{
  struct foo {};
  foo bar() { return foo(); }
  std::vector<foo*> bar2() { 
    return std::vector<foo*>(1, new foo); 
  } 
%}

%template(FooVec) std::vector<foo*>;

%extend std::vector<foo*> {
  void empty_and_delete() {
    for (std::vector<foo*>::iterator it = $self->begin(); 
         it != $self->end(); ++it) {
      delete *it;
    }
    $self->clear();
  }
}
Run Code Online (Sandbox Code Playgroud)

我们可以这样做:

Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> x = test.bar2()
>>> print x.size()
1
>>> x.empty_and_delete()
>>> print x.size()
0
>>> 
Run Code Online (Sandbox Code Playgroud)

或者您可以使用%pythoncode修改__del__来自动调用该函数,但这不是一个好主意,因为它不会影响Python根本看不到的对象,并且可能在少数情况下导致意外行为.

  • 精彩分析.就在我认为我理解SWIG界面文件时,我发现我仍然没有理解它.你能解释为什么要移动%{struct foo {}; %}内联 (3认同)
  • 再次感谢您的明确解释.非常感谢!! (3认同)
  • @Jason里面`%{%}`里面的所有东西都按原样传递给生成的包装器文件,但是它没有直接"暴露"到目标语言.您通常会将"生成的接口所需的内容正确编译",通常是"#include"或您想要依赖的其他机制.在SWIG接口文件中看到的内容被包装,但是定义/声明不会直接传递给生成的接口文件.`%inline`是一个快捷方式,表示"我想包装这个*并*通过逐字传递声明/定义" (2认同)
  • 所以它也可以写成`%{struct foo {}; %} struct foo {};`而不是使用`%inline`.重要的是SWIG接口中的`struct foo {};`与`struct foo;`不同 - 前者暗示了一个默认的析构函数/构造函数,但后者暗示了一个只能使用的不透明指针作为一个句柄. (2认同)