2013-09-21 3 views
15

メモリを割り当ててポインタを返すC/C++コードを考えてみましょう。Python:ctypesを使ったガベージコレクタの動作

#include <stdlib.h> 

#ifdef __cplusplus 
    extern "C" { 
#endif 

void Allocate(void **p) { 
int N=2048; 
*p=malloc(N); 
} 

#ifdef __cplusplus 
} 
#endif 

明らかにそのブロックを解放するのは私の責任だと思っています。 これを共有ライブラリにコンパイルし、ctypesでPythonから呼び出すが、明示的にそのメモリを解放しないとします。

import ctypes 
from ctypes import cdll, Structure, byref 
external_lib = cdll.LoadLibrary('libtest.so.1.0') 
ptr=ctypes.c_void_p(0) 
external_lib.Allocate(ctypes.byref(ptr)) 

私はvalgrindのと、このスクリプトを実行する場合、私は「-O3」フラグなしTEST.CPPをコンパイルする場合は、私が2048バイトのメモリリークを取得します。しかし、 '-O3'フラグを付けてコンパイルすると、メモリリークは発生しません。

これは実際問題ではありません。割り当てたメモリを明示的に解放するように常に注意します。しかし、私はこの行動がどこから来るのか興味があります。

これをlinuxの以下のスクリプトでテストしました。

==27875== LEAK SUMMARY: 
==27875== definitely lost: 2,048 bytes in 1 blocks 
==27875== indirectly lost: 0 bytes in 0 blocks 
==27875==  possibly lost: 295,735 bytes in 1,194 blocks 
==27875== still reachable: 744,633 bytes in 5,025 blocks 
==27875==   suppressed: 0 bytes in 0 blocks 

報::次の出力

報と

g++ -Wall -c -fPIC -fno-common test.cpp -o libtest1.o 
g++ -shared -Wl,-soname,libtest1.so.1 -o libtest1.so.1.0 libtest1.o 

g++ -O3 -Wall -c -fPIC -fno-common test.cpp -o libtest2.o 
g++ -shared -Wl,-soname,libtest2.so.1 -o libtest2.so.1.0 libtest2.o 

valgrind python test1.py &> report1 
valgrind python test2.py &> report2 

==27878== LEAK SUMMARY: 
==27878== definitely lost: 0 bytes in 0 blocks 
==27878== indirectly lost: 0 bytes in 0 blocks 
==27878==  possibly lost: 295,735 bytes in 1,194 blocks 
==27878== still reachable: 746,681 bytes in 5,026 blocks 
==27878==   suppressed: 0 bytes in 0 blocks 
+1

import ctypes from ctypes import cdll, Structure, byref external_lib = cdll.LoadLibrary('libtest.so.1.0') ptr=ctypes.c_void_p(0) external_lib.Allocate(ctypes.byref(ptr)) external_lib.Allocate(ctypes.byref(ptr)) # <-- Allocate a second block, the first becomes lost. 

ここでは、両方の到達可能と到達不能ブロックを示すvalgrindのからの結果の抜粋です。 'Python 3.3.2'では、2つのレポートの両方で2048バイトのリークが発生しますが、Python 2.7.5ではどちらのレポートでもリークはありません。 'Linux 3.11.4 x86_64'で' gcc 4.8.1 20130725'とテストされました – starrify

答えて

-1

この動作はgcc -O3の最適化から来ます。 gccは割り当てられたメモリが未使用であることを確認し、このコードブロックを省略します。

あなたはこの質問を参照することができます。malloc and gcc optimization 2

+0

私はそれがここに当てはまるとは思わない。あなたが言及している質問では、割り当てられたアドレスは決してループを離れず、決して使用されません。ここでは、アドレスは引数のポインタによってどこかに書かれているため、「未使用」とマークすることはできません。明らかに間違った答えのために – viraptor

+0

-1。少なくともバイナリを逆アセンブルしてみるか、 'gcc -S'の結果を見て、' malloc'を含むコードが削除されるかどうかを確認してください。 – starrify

4

異なるユーザが自分のプラットフォームに応じて、異なる結果を得るように見えます。私はPython 2.5.5、Python 2.6.8、Python 3.2.3、g ++ 4.7.2のDebian Wheezyシステムでこの問題をうまく再現しようとしました。

あなたのコードに基づいて、あなたはそれが漏れていることを知っています。それはvalgrindがメモリ使用量を違う方法で報告しているということだけです。レポート1では、2048チャンクへの参照はまったくありません。レポート2では、still reachableセクションに記載されています。

valgrind leak detector documentationには、リークがどのように検出されるかが記載されています。興味深いのは、メモリと各スレッドの汎用レジスタセットの両方で参照が検索されることです。リークディテクタがプログラムの終了時に実行されても、割り当てられたメモリに対するCPUレジスタの1つに依然として参照があることは考えられます(しかし、そうは思わないでしょう)。最適化されていないバージョンについては、漏れた参照を含む可能性のあるレジスタ情報を壊す追加の命令がAllocate関数に存在する可能性があります。最適化されたバージョンでは、Allocate関数はレジスタ内の参照を保持し、結果を*pに格納することもできます。

もちろん、これを再現することができなくても、それはまったくのものです。 valgrindに、検索された参照に関する詳細情報を出力するよう要求することができます。この情報は、割り当てられたブロックについてのより詳細な情報を提供します。

例えば、到達可能なブロックと到達不能なブロックの両方が表示されます。

valgrind --show-reachable=yes --leak-check=full python2.5 test1.py &> report1-2.5 

私はあなたのコードは以下のように変更した場合、私のシステム上のすべてのテストは、(4096バイトが割り当てられているにもかかわらず)2048ブロックが確実に失われていることを示しています。これはvalgrindのリークディテクタによって取り込まれているキャッシュされたレジスタ値の一種かもしれないと私は信じています。私はあなたの手順に従ってきたし、結果は興味深いもの

==28844== 2,048 bytes in 1 blocks are still reachable in loss record 305 of 366 
==28844== at 0x4C28BED: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) 
==28844== by 0x6CD870F: Allocate (in /projects/stack-overflow/18929183-python-garbage-collector-behavior-with-ctypes/libtest1.so.1.0) 
==28844== by 0x6ACEDEF: ffi_call_unix64 (in /usr/lib/python2.6/lib-dynload/_ctypes.so) 
==28844== by 0x6ACE86A: ffi_call (in /usr/lib/python2.6/lib-dynload/_ctypes.so) 
==28844== by 0x6AC9A66: _CallProc (callproc.c:816) 
==28844== by 0x6AC136C: CFuncPtr_call (_ctypes.c:3860) 
==28844== by 0x424989: PyObject_Call (abstract.c:2492) 
==28844== by 0x4A17B8: PyEval_EvalFrameEx (ceval.c:3968) 
==28844== by 0x49F0D1: PyEval_EvalCodeEx (ceval.c:3000) 
==28844== by 0x49F211: PyEval_EvalCode (ceval.c:541) 
==28844== by 0x4C66FE: PyRun_FileExFlags (pythonrun.c:1358) 
==28844== by 0x4C7A36: PyRun_SimpleFileExFlags (pythonrun.c:948) 
==28844== 
==28844== 2,048 bytes in 1 blocks are definitely lost in loss record 306 of 366 
==28844== at 0x4C28BED: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) 
==28844== by 0x6CD870F: Allocate (in /projects/stack-overflow/18929183-python-garbage-collector-behavior-with-ctypes/libtest1.so.1.0) 
==28844== by 0x6ACEDEF: ffi_call_unix64 (in /usr/lib/python2.6/lib-dynload/_ctypes.so) 
==28844== by 0x6ACE86A: ffi_call (in /usr/lib/python2.6/lib-dynload/_ctypes.so) 
==28844== by 0x6AC9A66: _CallProc (callproc.c:816) 
==28844== by 0x6AC136C: CFuncPtr_call (_ctypes.c:3860) 
==28844== by 0x424989: PyObject_Call (abstract.c:2492) 
==28844== by 0x4A17B8: PyEval_EvalFrameEx (ceval.c:3968) 
==28844== by 0x49F0D1: PyEval_EvalCodeEx (ceval.c:3000) 
==28844== by 0x49F211: PyEval_EvalCode (ceval.c:541) 
==28844== by 0x4C66FE: PyRun_FileExFlags (pythonrun.c:1358) 
==28844== by 0x4C7A36: PyRun_SimpleFileExFlags (pythonrun.c:948)