【问题标题】:SWIG and C++ memory leak with vector of pointers带有指针向量的 SWIG 和 C++ 内存泄漏
【发布时间】:2012-11-15 06:49:02
【问题描述】:

我正在使用 SWIG 在 C++ 和 Python 之间进行接口。我创建了一个创建对象指针的 std::vector 的函数。在这种情况下,指向的对象并不重要。

我遇到的问题是,当对象 (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 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。

【问题讨论】:

    标签: c++ python-2.7 swig


    【解决方案1】:

    您看到的警告并不直接在于您有一个指针向量这一事实。考虑以下 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 代理对象在此处释放/删除(视情况而定)底层对象,但它无法仅根据它所看到的前向声明推断出如何执行此操作。

    如果我们扩展测试用例以包含std::vector&lt;foo&gt;,则会观察到相同的情况:

    %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&lt;T&gt;,我们需要使用%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&lt;T*&gt;,因此我们可以更改我们的测试用例来说明:

    %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,除非你真的希望指向的对象比向量长。基本上你可以:

    1. 不在结构中存储指针
    2. 使用智能指针(std::shared_ptrstd::unique_ptr 或 boost 等价物代替)。
    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> >;
    
    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 根本看不到的对象,并且在某些情况下可能会导致意外行为。

    【讨论】:

    • 出色的分析。就在我以为我理解了 SWIG 接口文件时,我发现我仍然不明白。你能解释一下为什么要移动 %{ struct foo {}; %} 内联
    • 精妙的分析....感谢您提供清晰的示例。你能解释一下为什么(示例 1)从 %{ struct foo {}; 移动 foo 的定义吗? %} 到 %inline 会改变行为吗?
    • @Jason %{ %} 中的所有内容都按原样传递给生成的包装文件,但它不会直接“暴露”给目标语言。您通常将“生成的接口正确编译所需的东西”放入其中,通常是#include 或您想要依赖的其他机制。在 SWIG 接口文件中可以看到的内容被包装,但定义/声明不会直接传递给生成的接口文件。 %inline 是说“我想包装这个逐字传递声明/定义”的快捷方式
    • 所以也可以写成%{ struct foo {}; %} struct foo {};而不是%inline。重要的是,SWIG 接口中的struct foo {};struct foo; 不同——前者暗示了需要的默认析构函数/构造函数,而后者暗示了一个只能用作句柄的不透明指针。跨度>
    • 如果所有 SO 答案都像你的一样!谢谢
    猜你喜欢
    • 2016-05-14
    • 2018-10-18
    • 2022-01-26
    • 1970-01-01
    • 2013-10-31
    • 2015-12-15
    • 2019-04-18
    • 2017-03-10
    • 1970-01-01
    相关资源
    最近更新 更多