2013-09-21 61 views
15

Vì vậy, giả sử mã C/C++ phân bổ một số bộ nhớ và trả về một con trỏ đến nó.Python: hành vi thu gom rác thải với ctypes

#include <stdlib.h> 

#ifdef __cplusplus 
    extern "C" { 
#endif 

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

#ifdef __cplusplus 
} 
#endif 

Tôi hy vọng rằng đó là trách nhiệm của tôi để giải phóng khối bộ nhớ đó. Bây giờ giả sử tôi biên dịch nó thành một thư viện được chia sẻ và gọi nó từ Python với ctypes, nhưng không giải phóng bộ nhớ đó một cách rõ ràng.

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

Nếu tôi chạy tập lệnh này với valgrind, tôi bị rò rỉ bộ nhớ 2048 byte nếu tôi biên dịch test.cpp không có cờ '-O3'. Nhưng nếu tôi biên dịch nó với lá cờ '-O3', thì tôi không bị rò rỉ bộ nhớ.

Nó không thực sự là một vấn đề - tôi sẽ luôn luôn cẩn thận để giải phóng một cách rõ ràng bất kỳ bộ nhớ nào tôi phân bổ. Nhưng tôi tò mò về hành vi này đến từ đâu.

Tôi đã thử nghiệm điều này bằng tập lệnh sau trong 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 

với sản lượng sau

Report1:

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

Tôi đã theo bước của bạn và kết quả là thú vị. Với 'Python 3.3.2' hai báo cáo đều cho rò rỉ với 2048 byte, tuy nhiên với' Python 2.7.5' thì không có rò rỉ nào với báo cáo. Thử nghiệm trong 'Linux 3.11.4 x86_64' với 'gcc 4.8.1 20130725' – starrify

Trả lời

-1

Hành vi này xuất phát từ optimisations O3 gcc. gcc thấy rằng bộ nhớ được cấp phát không được sử dụng và bỏ qua khối mã này.

Bạn có thể tham khảo những câu hỏi này: malloc and gcc optimization 2

+0

Tôi không nghĩ đó là trường hợp ở đây. Trong câu hỏi bạn đề cập, địa chỉ được phân bổ không bao giờ rời khỏi vòng lặp và không bao giờ được sử dụng. Ở đây, địa chỉ được viết ở đâu đó thông qua con trỏ của đối số, vì vậy nó không thể được đánh dấu "không sử dụng". – viraptor

+0

-1 cho câu trả lời rõ ràng không chính xác. Vui lòng ít nhất hãy thử tháo nhị phân hoặc xem kết quả của 'gcc -S' để xác minh xem mã có chứa' malloc' sẽ bị loại bỏ hay không. – starrify

4

người dùng khác nhau xuất hiện để có được kết quả khác nhau tùy thuộc vào nền tảng của họ. Tôi đã cố gắng tạo lại vấn đề này không thành công trên hệ thống Debian Wheezy với Python 2.5.5, Python 2.6.8, Python 3.2.3 với g ++ 4.7.2.

Dựa trên mã của bạn, bạn biết rằng nó bị rò rỉ, nó chỉ là valgrind đang báo cáo việc sử dụng bộ nhớ khác nhau. Trong báo cáo 1, chắc chắn không có tham chiếu đến đoạn 2048. Trong báo cáo 2, nó được liệt kê trong phần still reachable.

valgrind leak detector documentation mô tả cách phát hiện rò rỉ. Thật thú vị khi lưu ý rằng nó tìm kiếm các tham chiếu trong cả bộ nhớ và thanh ghi mục đích chung được đặt cho mỗi luồng. Nó sẽ là conceivable (nhưng tôi đã có thể nghĩ rằng không) rằng khi phát hiện rò rỉ chạy trên lối ra chương trình, vẫn còn một tài liệu tham khảo trong một trong những CPU đăng ký vào bộ nhớ đã được phân bổ. Đối với phiên bản chưa được tối ưu hóa, các hướng dẫn bổ sung có thể tồn tại trong chức năng Allocate làm mất mọi thông tin đăng ký có thể chứa tham chiếu bị rò rỉ. Trên phiên bản được tối ưu hóa, có thể cho chức năng Allocate để giữ lại tham chiếu trong sổ đăng ký cũng như lưu trữ kết quả trong *p.

Tất nhiên, không có khả năng tái tạo điều này, tất cả đều là phỏng đoán. Bạn có thể yêu cầu valgrind để xuất thêm thông tin về các tài liệu tham khảo mà nó tìm thấy có thể cung cấp thông tin chi tiết hơn về các khối được phân bổ.

ví dụ: Điều này sẽ hiển thị cả các khối có thể truy cập và không thể truy cập.

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

Nếu tôi sửa đổi mã của bạn để có những điều sau đây, tất cả các bài kiểm tra trên hệ thống của tôi chỉ ra rằng một khối 2048 là chắc chắn bị mất (mặc dù 4096 byte đã được phân bổ). Điều này cũng khiến tôi tin rằng nó có thể là một loại giá trị đăng ký được lưu trữ trong bộ nhớ đang được dò tìm bởi máy dò rò rỉ của 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. 

Sau đây là đoạn kết quả từ valgrind cho thấy cả một khối thể truy cập và không thể truy cập:

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