2013-09-21 2 views
15

그래서 메모리를 할당하고 포인터를 반환하는 C/C++ 코드를 가정 해 보겠습니다.파이썬 : ctypes를 사용한 가비지 컬렉터 동작

#include <stdlib.h> 

#ifdef __cplusplus 
    extern "C" { 
#endif 

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

#ifdef __cplusplus 
} 
#endif 

분명히 그 메모리 블록을 해제해야 할 책임이 있습니다. 이것을 공유 라이브러리로 컴파일하고 ctypes로 파이썬에서 호출한다고 가정하되, 명시 적으로 그 메모리를 해제하지는 마십시오. 내가 Valgrind의이 스크립트를 실행하면 나는 '-03'플래그없이 Test.cpp에 컴파일하는 경우

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

, 나는 2048 바이트의 메모리 누수를 얻을. 하지만 '-O3'플래그로 컴파일하면 메모리 누수가 발생하지 않습니다.

정말 문제는 아닙니다. 할당 한 메모리를 명시 적으로 해제하는 데 항상주의해야합니다. 그러나 나는이 행동이 어디에서 유래하는지 궁금하다.

나는 이것을 리눅스에서 다음 스크립트로 테스트했다. 다음과 같은 출력

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 

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

나는 당신의 단계를 수행 한 결과는 흥미 롭다. 'Python 3.3.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

다른 사용자가 자신의 플랫폼에 따라 다른 결과를 얻을 것으로 보인다. 파이썬 2.5.5, 파이썬 2.6.8, 파이썬 3.2.3, g ++ 4.7.2의 데비안 휘지 시스템에서이 문제를 성공적으로 재현하려고 시도했습니다.

코드를 기반으로하면 코드가 새어 나가는 것을 알 수 있습니다. valgrind가 메모리 사용량을 다르게보고하고있는 것입니다. 보고서 1에는 2048 청크에 대한 참조가 전혀 없습니다. 보고서 2의 내용은 still reachable 섹션에 있습니다.

valgrind leak detector documentation은 누출이 감지되는 방법을 설명합니다. 흥미로운 점은 메모리와 각 스레드에 설정된 범용 레지스터에서 참조를 찾습니다. 누수 감지기가 프로그램 종료시 실행될 때 할당 된 메모리에 대한 CPU 레지스터 중 하나에 여전히 참조가 있음을 알 수 있습니다 (그러나 나는 생각할 수 없을 것입니다). 최적화되지 않은 버전의 경우, 누설 된 참조를 포함 할 수있는 레지스터 정보를 모두 훼손하는 추가 지침이 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)