首页 文章

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

提问于
浏览
20

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

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

示例

  • C代码:
std::vector < someObject* > createSomeObjectForPython()
{
   std::vector < someObject* > myVector;
   someObject* instanceOfSomeObject = new someObject();
   myVector.push_back(instanceOfSomeObject);
   return myVector;
}
  • 来自Python解释器:
objectVar = createSomeObjectForPython()

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

swig/python detected a memory leak of type 'std::vector< someObject *,std::allocator<  someObject * > > *', no destructor found.

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

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

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

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

1 回答

  • 41

    你看到的警告并不直接与你有一个指针向量的事实有关 . 请考虑以下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; }
    %}
    

    使用此界面可以:

    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.
    

    问题是SWIG只看到了声明,而不是 struct foo 的定义 . 默认行为是Python代理对象在这里释放/删除(根据需要)底层对象,但它看到了_2370922 .

    如果我们将测试用例扩展到包含 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>(); 
      } 
    %}
    

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

    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.
    

    但是,我们可以通过确保类型的定义可用来轻松解决这个问题 . 对于 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>;
    

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

    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> >
    >>>
    

    复杂的是,在您的示例中,您有 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*>;
    

    然后我们可以运行:

    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> >
    >>>
    

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

    至于如何处理泄漏,选项与C中的通常相同 . 就个人而言,我会尝试avoid putting raw pointers in a vector,除非你真的希望指向的对象比向量更长 . 基本上你可以:

    • 不在结构中存储指针

    • 使用智能指针( std::shared_ptrstd::unique_ptr 或增强等效项) .

    • 以某种方式手动管理内存 .

    我们在第二个例子中已经完成了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> >;
    
    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>
    >>>
    

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

    如果你真的想要做第三种方式(我会不惜一切代价避免它,因为它让你的界面对人为错误开放)用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();
      }
    }
    

    我们可以这样做:

    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
    >>>
    

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

相关问题