I have a class that upon initialization, reserves an array of pointers with "new". Upon destruction, that array of pointers is released with "delete[]".
There's no problem when running the code without python. However, when I "swig'it" and use it as a python module, something weird happens. The destructor is called correctly upon garbage collection, but when doing that, a memory leak occurs! A complete mystery (at least to me). Help highly appreciated!
(1) Preliminaries to get it compiled:
setup.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
%module pps
%{
#define SWIG_FILE_WITH_INIT
%}
%inline %{
class MemoryManager {
public:
MemoryManager(int n);
~MemoryManager();
};
%}
(2) The C++ code itself:
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) A test python program:
test.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")
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
EDIT AND OFF-TOPIC
As a question of this characteristics always attracts people who think they can fix everything by using containers instead of raw pointer structures, here is the container version (might be wrong .. not used vectors much, but anyway, its off-topic):
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");
}
Doesn't work either
FINAL REMARKS
Thank you for Flexos for amazing/detailed analysis.
My initial reason for using inconsistent class declaration was, that I routinely don't want to expose all details of my class in python.
I forgot that in Swig interface file, everything that's put in the
%{..}
is prepended into the generated wrapper code .. now that was missing, so the wrapper code got the class declaration only from the %inline
section..!
We can still have a minimal part of a the class wrapped, with the following pps.i file:
%module pps
%{
#define SWIG_FILE_WITH_INIT
#include "pps.h"
%}
class MemoryManager {
public:
MemoryManager(int n);
~MemoryManager();
};
Where "pps.h" should have the correct class declaration. Now #included "pps.h"
appears in the beginning of "pps_wrap.cpp".
The "class declaration" in "pps.i" only tells Swig what we want to wrap ..
.. right..? (no more memory errors, at least)