16

では、メモリを割り当ててポインタを返す IC/C++ コードを考えてみましょう。

#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で次のスクリプトを使用してこれをテストしました。

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

次の出力で

レポート 1:

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

レポート 2:

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

2 に答える 2

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 リーク検出器のドキュメントには、リークの検出方法が説明されています。興味深いことに、メモリと各スレッドの汎用レジスタ セットの両方で参照が検索されます。プログラムの終了時にリーク検出器が実行されているときに、割り当てられたメモリへの参照が CPU レジスタの 1 つに残っていることは考えられます (しかし、私はありそうもないと思いました)。最適化されていないバージョンのAllocate場合、リークされた参照を含む可能性のあるレジスタ情報を上書きする追加の命令が関数に存在する可能性があります。最適化されたバージョンでは、Allocate関数が参照をレジスタに保持し、結果を に格納することが可能*pです。

もちろん、これを再現できなくても、すべて推測です。valgrind割り当てられたブロックについてより多くの洞察を提供する可能性がある、見つかった参照に関する詳細情報の出力を要求できます。

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

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

コードを次のように変更すると、システムでのすべてのテストで、2048 ブロックが確実に失われていることが示されます (4096 バイトが割り当てられているにもかかわらず)。これはまた、valgrind のリーク検出器によって検出されている、ある種のキャッシュされたレジスタ値である可能性があると私に信じさせます。

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 からの結果のスニペットを次に示します。

==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)
于 2013-10-17T02:18:14.197 に答える
-1

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

この質問を参照できます: malloc と gcc の最適化 2

于 2013-10-13T09:07:17.923 に答える