SWIG和C ++内存泄漏与指针向量 [英] SWIG and C++ memory leak with vector of pointers

查看:411
本文介绍了SWIG和C ++内存泄漏与指针向量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用SWIG来连接C ++和Python。我创建了一个函数,创建一个std ::向量的对象指针。



我遇到的问题是,当对象( someObject )在Python端超出范围,它不能释放向量内对象/ s指针指向的内存,从而导致内存泄漏。



示例




  • 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检测到类型为'std :: vector< someObject *,std :: allocator< someObject *> > *',没有析构函数。 

这个错误是因为当Python删除向量时,它只能删除向量中的指针



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



我真的需要使用与对象的向量相反的指针向量,在任何人都认为这是一个解决方案,特别是因为对象是大而复杂,速度是一个问题。



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

解决方案

你看到的警告并不直接取决于你有一个指针的向量。考虑下面的SWIG界面文件:

 %module test 

//直接传递并不用于换行
%{
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(默认,2012年8月1日,05:16:07)
[GCC 4.6.3] on linux2
键入help,copyright信用或许可证。
>>>>导入测试
>>> test.bar()
< Swig 0xb7654a70>类型为struct foo *的对象。
>>>>
swig / python检测到类型为'struct foo *'的内存泄漏,找不到析构函数。问题是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>();
}
%}

这里再次提供关于无析构函数的警告: / p>

  Python 2.7.3(默认,2012年8月1日,05:16:07)
[GCC 4.6.3] on linux2
更多信息,请输入帮助,版权,信用或许可证。
>>>>导入测试
>>> print test.bar2()
< Swig'std :: vector< foo,std :: allocator< foo> > *'at 0xb7671a70> swig / python检测到类型为'std :: vector< foo,std :: allocator< foo> > *',没有析构函数。

但是,我们可以通过确保类型的定义可用。对于 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(默认,2012年8月1日,05:16:07)
[GCC 4.6.3 ] on linux2
更多信息,请输入help,copyright,credits或license。
>>>>导入测试
>>> print test.bar()
< test.foo;代理< Swig对象的类型'foo *'在0xb76aba70> >
>>>> print test.bar2()
< test.FooVec;代理< Swig对象的类型'std :: vector< foo> *'at 0xb76abab8> >
>>>>

复杂性是,在你的例子中,你有 std :: vector< T *> ,因此我们可以更改我们的测试用例来说明:

 %module test $ 
$ b%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(默认,2012年8月1日,05:16:07)
[GCC 4.6.3] on linux2
类型帮助,版权,信用或许可证。
>>>>导入测试
>>> print test.bar2()
< test.FooVec;代理< Swig对象的类型'std :: vector< foo *> *'at 0xb7655a70> >
>>>>

em>显示你注意到的警告,因为就SWIG而言, std :: vector 本身已被正确删除(事实上与C ++完全相同的语义)。



至于如何处理泄漏,选项与C ++中的通常一样。我个人会尝试避免将原始指针放在向量,除非你真的想要指向的对象比向量更长。基本上你可以:


  1. 不将指针存储在结构中

  2. 使用智能指针$ c> std :: shared_ptr std :: unique_ptr 或提升等效项)。



  3. 我们已经在第二个例子中做了1。使用SWIG 2也很简单,3是在您的界面中编写和封装另一个函数的问题。

     %module test 

    %include< std_vector.i>
    %includes< 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> > ;;



      default,Aug 1 2012,05:16:07)
    [GCC 4.6.3] on linux2
    更多信息,请输入帮助,版权,信用或许可证。
    >>>>导入测试
    >>> print test.bar2()
    < test.FooVec;代理< Swig对象的类型'std :: vector< std :: shared_ptr< foo>,std :: allocator< std :: shared_ptr< foo> > > *'at 0xb76f4a70> >
    >>>> print test.bar2()[0]
    < Swig'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(默认,2012年8月1日,05:16:07)
    [GCC 4.6。 3] on linux2
    更多信息,请输入help,copyright,credits或license。
    >>>>导入测试
    >>> x = test.bar2()
    >>>> print x.size()
    1
    >>> x.empty_and_delete()
    >>>> print x.size()
    0
    >>>

    或者您可以使用%pythoncode 修改 __del __ 自动调用函数,但这将是一个坏主意,因为它不会影响Python从来没有看到的对象,并可能导致意想不到的行为在少数情况下。


    I am using SWIG to interface between C++ and Python. I have created a function which creates a std::vector of object pointers. The objects that are pointed to are not important in this case.

    The problem I have is that when the object (someObject) goes out of scope on the Python side it cannot free the memory pointed to by the object/s pointers within the vector, thus causing a memory leak.

    Example

    • C++ code:

      std::vector < someObject* > createSomeObjectForPython()
      {
         std::vector < someObject* > myVector;
         someObject* instanceOfSomeObject = new someObject();
         myVector.push_back(instanceOfSomeObject);
         return myVector;
      }
      

    • From the Python interpreter:

      objectVar = createSomeObjectForPython()
      

    When I run this in Python I get this error:

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

    This error is because when Python deletes the vector, it can only delete the pointers within the vector and not actually what they point to.

    If I could create a destructor for std::vector, this would be the answer, but it’s not possible.

    I really need to use vectors of pointers opposed to vectors of objects before anyone suggests this as a solution, particularly because the objects are large and complex, and speed is an issue.

    I am using gcc4.4, swigwin 2.0.4, and Python 2.7 on Windows.

    解决方案

    The warning you see doesn't lie directly with the fact that you have a vector of pointers. Consider the following SWIG interface file:

    %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; }
    %}
    

    Using this interface gives:

    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.
    

    The problem is that SWIG has only seen a declaration, not a definition for struct foo. The default behaviour is for the Python proxy object to free/delete (as appropriate) the underlying object here, but it's not able to deduce how to do that based on only the forward declaration it's seen.

    If we extend the test case to include std::vector<foo> the same is observed:

    %module test
    
    %{
    struct foo {};
    %}
    
    struct foo;
    
    %include <std_vector.i>
    
    %inline %{
      foo bar() { return foo(); }
      std::vector<foo> bar2() { 
        return std::vector<foo>(); 
      } 
    %}
    

    Which again gives the warning about no destructor:

    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.
    

    However we can trivially fix this by making sure a definition of the type is available. For struct foo that's simply making the whole body of the struct visible to SWIG. For std::vector<T> we need to use %template to do that:

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

    Which now doesn't warn (or leak for that matter):

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

    The complication is that in your example you have std::vector<T*>, so we can alter our test case to illustrate that:

    %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*>;
    

    Which we can then run:

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

    This does leak, but crucially doesn't show the warning you noticed, because as far as SWIG is concerned the std::vector itself has been correctly deleted (the exact same semantics as in C++ in fact).

    As far as how to deal with the leak there the options are the same as usual in C++. Personally I'd try to avoid putting raw pointers in a vector unless you really want the objects pointed at to outlive the vector. Basically you can:

    1. Not store pointers in the struct
    2. Use smart pointers (std::shared_ptr or std::unique_ptr or boost equivalents instead).
    3. Manage the memory manually somehow.

    We've already done 1 in the second example. With SWIG 2 is pretty simple as well and 3 is a question of writing and wrapping another function in your interface.

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

    Which works, stores shared pointers and doesn't leak.

    If you really want to do the third way (I'd avoid it at all cost given that it leaves your interface open to human errors) the easiest way to do it with SWIG is to use %extend, for example:

    %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();
      }
    }
    

    The we can do:

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

    Or you could use %pythoncode to modify __del__ to call the function automatically, but that would be a bad idea because it wouldn't affect objects Python never sees at all and could lead to unexpected behaviour in a few cases.

    这篇关于SWIG和C ++内存泄漏与指针向量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆