SWIG内存泄漏

2024-09-30 22:23:11 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个类,在初始化时,用“new”保留一个指针数组。销毁后,指针数组将以“delete[]”释放。在

在不使用python的情况下运行代码没有问题。然而,当我“swig'it”并将其用作python模块时,会发生一些奇怪的事情。析构函数在垃圾收集时被正确调用,但在执行此操作时,会发生内存泄漏!完全是个谜(至少对我来说)。非常感谢帮助!在

(1)编制准备工作:

在设置.py在

from setuptools import setup, Extension, find_packages
import os
import copy
import sys

def make_pps():
  d=[]
  c=[]
  l=[]
  lib=[]
  s=[]
  s+=["-c++"]
  return Extension("_pps",sources=["pps.i","pps.cpp"],include_dirs=d,extra_compile_args=c,extra_link_args=l,libraries=lib,swig_opts=s)

ext_modules=[]
ext_modules.append(make_pps())  

setup(
  name = "pps",
  version = "0.1",
  packages = find_packages(),
  install_requires = ['docutils>=0.3'],
  ext_modules=ext_modules
)

pps.i.公司

^{pr2}$

(2)C++代码本身:

在pps.cpp公司在

#include <iostream>                             
#include <stdio.h>
#include <typeinfo>
#include <sys/time.h>
#include <stdint.h>  
#include <cmath>                       

#include <cstring>
#include <string.h>
#include <stdlib.h>

#include<sys/ipc.h> // shared memory
#include<sys/shm.h>


/*
Stand-alone cpp program: 
  - UNComment #define USE_MAIN (see below)
  - compile with

    g++ pps.cpp

  - run with

    ./a.out

    => OK

  - Check memory leaks with

    valgrind ./a.out

    => ALL OK/CLEAN


Python module:
  - Comment (i.e. use) the USE_MAIN switch (see below)
  - compile with

    python3 setup.py build_ext; cp build/lib.linux-x86_64-3.5/_pps.cpython-35m-x86_64-linux-gnu.so ./_pps.so

  - run with

    python3 test.py

    => CRASHHHHH

  - Check memory leaks with

    valgrind python3 test.py

    => Whoa..

  - Try to enable/disable lines marked with "BUG?" .. 

*/

// #define USE_MAIN 1 // UNcomment this line to get stand-alone c-program


using std::cout; 
using std::endl;
using std::string;


class MemoryManager {

public:
  MemoryManager(int n);
  ~MemoryManager();

private:
  int nmax;
  int nc; // next index to be used
  uint nsum;
  int* ids;
  void** buffers;
};


MemoryManager::MemoryManager(int n) : nmax(n),nc(0) {
  cout << "MemoryManager: nmax="<<this->nmax<<"\n";

  this->buffers  =new void*[this->nmax]; // BUG?
  this->ids      =new int  [this->nmax];
  this->nsum     =0;
}


MemoryManager::~MemoryManager() {
  printf("MemoryManager: destructor\n");
  delete[] this->buffers;               // BUG?
  delete[] this->ids;
  printf("MemoryManager: destructor: bye\n");
}


#ifdef USE_MAIN
int main(int argc, char *argv[]) {
  MemoryManager* m;

  m=new MemoryManager(1000);
  delete m;

  m=new MemoryManager(1000);
  delete m;
}
#endif

(3)一个测试python程序:

在测试.py在

from pps import MemoryManager
import time

print("creating MemoryManager")
mem=MemoryManager(1000)
time.sleep(1)
print("clearing MemoryManager")
mem=None
print("creating MemoryManager (again)")
time.sleep(1)
mem=MemoryManager(1000)
time.sleep(1)
print("exit")

编译时使用:

python3 setup.py build_ext; cp build/lib.linux-x86_64-3.5/_pps.cpython-35m-x86_64-linux-gnu.so ./_pps.so

运行方式:

python3 test.py

编辑和离题

关于这个特性的问题总是吸引那些认为可以通过使用容器而不是原始指针结构来修复一切的人,这里是容器版本(可能是错误的。。向量使用不多,但不管怎样,这不是主题):

class MemoryManager {

public:
  MemoryManager(int n);
  ~MemoryManager();

private:
  int nmax;
  int nc; // next index to be used
  uint nsum;

  // "ansi" version
  //int* ids;
  //void** buffers;

  // container version
  vector<int>   ids;
  vector<void*> buffers;
  // vector<shared_ptr<int>> buffers; // I feel so modern..
};


MemoryManager::MemoryManager(int n) : nmax(n),nc(0) {
  cout << "MemoryManager: nmax="<<this->nmax<<"\n";

  /* // "ansi" version
  this->buffers  =new void*[this->nmax]; // BUG?
  this->ids      =new int  [this->nmax];
  */

  // container version
  this->ids.reserve(10);
  this->buffers.reserve(10);

  this->nsum     =0;
}


MemoryManager::~MemoryManager() {
  printf("MemoryManager: destructor\n");

  /* // "ansi" version
  delete[] this->buffers;               // BUG?
  delete[] this->ids;
  */
  printf("MemoryManager: destructor: bye\n");
}

也不管用

最后评论

感谢您对Flexos的精彩/详细分析。在

我使用不一致的类声明的最初原因是,我通常不想在python中公开类的所有细节。在

我忘了在Swig接口文件中

%{..}

预先添加到生成的包装代码中。。现在丢失了,所以包装器代码只从%inline部分获得类声明。。!在

我们仍然可以使用以下pps.i文件包装类的最小部分:

%module pps
%{
#define SWIG_FILE_WITH_INIT
#include "pps.h"
%}

class MemoryManager {

public:
  MemoryManager(int n);
  ~MemoryManager();
};

其中“pps.h”应该有正确的类声明。现在#included "pps.h"出现在“pps”的开头_包装.cpp". 在

“pps.i”中的“class declaration”只告诉Swig我们要包装什么。。在

。。正确的。。?(至少不再有内存错误)


Tags: pyimportidsnewincludeversionwiththis
1条回答
网友
1楼 · 发布于 2024-09-30 22:23:11

你有些不确定的行为。这是一个有趣的,所以让我们仔细看看。对于那些想跟随我的人,我有一个手臂装置在手边,所以我将从中进行拆卸,以显示影响是什么。在

造成这种情况的根本原因是您错误地使用了%inline。最后的结果是,您向编译器展示了类MemoryManager的两个不同的定义。在你的.i文件中你写道:

%inline %{
class MemoryManager {    
public:
  MemoryManager(int n);
  ~MemoryManager();
};
%}

MemoryManager的定义中,重要的是两件事:

    <> LI>由于使用了{ },该类的特定定义将由编译WRAP的SWIG和C++编译器看到。pps.cpp公司翻译单位。在
  1. 此定义中不包含公共或私有成员变量。所以尺寸/布局会有问题。(它是类的定义而不是声明,即使构造函数/析构函数只是声明)。在

由于没有成员变量(并且空基类优化在这里不适用),这个类的大小至少是一个字节(因此它是唯一可寻址的),但是不能保证它的大小超过1个字节。这显然是错误的,因为在定义构造函数和析构函数的翻译单元中,你告诉编译器它比1字节大得多。从这一点开始,所有的赌注都被取消了,编译器可以自由地做任何它想做的事情。但在这个例子中,让我们看看它在ARM上实际做了什么。在

首先,我运行了:objdump -d build/temp.linux-armv7l-3.4/pps_wrap.o |c++filt并查找SWIG生成的_wrap_new_MemoryManager符号。这个函数是创建一个新的Python实例和在C++中调用新的桥梁,我们在这里寻找,因为这是bug本身的例子(在这个例子中)。大多数指令都是不相关的,所以为了简洁起见,我删去了一些不相关的东西:

^{pr2}$

^{} is the first argument,共operator new(unsigned int)。在上面的代码中,编译器将其设置为只分配1个字节。这样做是错误的,因为在这个翻译单元中,MemoryManager的定义只需要1个字节的存储来满足唯一寻址的要求。在

因此,当我们调用MemoryManager::MemoryManager(int)时,this指向的指针只是一个1字节的堆分配。一旦我们读/写过去,我们就会对堆做坏事。在这一点上,所有的赌注真的没有了。稍后可以在那里分配其他东西,Python运行时需要的任何东西。或者向量/new[]调用产生的分配。也可能是未映射的记忆,或者其他所有的东西。但不管发生什么都不好。在

< >比较一下,如果我们编译了C++,并启用了main函数(^ {CD13>}以回避对象),而得到了:

main:
        .fnstart
.LFB1121:
        @ args = 0, pretend = 0, frame = 16
        @ frame_needed = 1, uses_anonymous_args = 0
        push    {r4, r7, lr}
        .save {r4, r7, lr}
        .pad #20
        sub     sp, sp, #20
        .setfp r7, sp, #0
        add     r7, sp, #0
        str     r0, [r7, #4]
        str     r1, [r7]
        movs    r0, #20    <  This looks much more sensible!
.LEHB0:
        bl      operator new(unsigned int)
.LEHE0:
        mov     r4, r0
        mov     r0, r4
        mov     r1, #1000
.LEHB1:
        bl      MemoryManager::MemoryManager(int)

(对于gcc在默认优化设置下生成的代码,我有点惊讶)

因此,要真正解决这个问题,我建议您做两件事之一:

    把EM>所有EEE>你的C++内部^ ^ },并且根本没有其他.CPP文件。在
  1. 将类的定义移到一个.h文件中,并使用%include+#include在任何地方引用一致的定义。在

在我看来,除了最小的项目外,2号是最好的选择。所以你的SWIG文件将变得简单:

%module pps
%{
#define SWIG_FILE_WITH_INIT
#include "pps.h"
%}

%include "pps.h"
%}

(您还违反了自管理缓冲区代码中的rule of three,而且没有提供在基于向量的代码中免费获得的强大的异常安全保证,因此确实值得坚持下去)。在

相关问题 更多 >