Python:垃圾收集器行为与 ctypes

标签 python garbage-collection ctypes

假设我的 C/C++ 代码分配了一些内存,并返回指向它的指针。

#include <stdlib.h>

#ifdef __cplusplus
  extern "C" {
#endif

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

#ifdef __cplusplus
 }
#endif

显然,我希望释放那 block 内存是我的责任。 现在假设我将它编译成一个共享库并使用 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 运行此脚本,如果我编译 test.cpp 时不带“-O3”标志,则会出现 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

最佳答案

不同的用户似乎会根据他们的平台获得不同的结果。我试图在使用 Python 2.5.5、Python 2.6.8、Python 3.2.3 和 g++ 4.7.2 的 Debian Wheezy 系统上重现此问题,但未成功。

根据您的代码,您知道它存在漏洞,只是 valgrind 以不同方式报告内存使用情况。在报告 1 中,绝对没有提到 2048 block 。在报告 2 中,它列在 still reachable 部分。

valgrind leak detector documentation描述了如何检测泄漏。有趣的是,它会在内存和为每个线程设置的通用寄存器中查找引用。可以想象(但我认为不太可能)当泄漏检测器在程序退出时运行时,其中一个 CPU 寄存器中仍然存在对已分配内存的引用。对于未优化的版本,Allocate 函数中可能存在其他指令,这些指令会破坏可能包含泄漏引用的任何寄存器信息。在优化版本中,Allocate 函数可以在寄存器中保留引用并将结果存储在 *p 中。

当然,无法重现,这一切都是猜测。您可以请求 valgrind 输出有关它找到的引用的更多信息,这可能会提供有关分配 block 的更多信息。

例如。这将显示可到达和不可到达的 block 。

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

如果我将您的代码修改为以下内容,我系统上的所有测试都表明肯定丢失了 2048 block (即使已分配 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 的结果片段,显示了可到达和不可到达的 block :

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

关于Python:垃圾收集器行为与 ctypes,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18929183/

相关文章:

python - Pandas/Numpy 从数组列中获取矩阵

python - 对于打开的行(文件名)

python - 找不到 ctypes 错误 AttributeError 符号,OS X 10.7.5

python - 省略号对象有什么作用?

python - 如何从 3d 张量向每个 2d 张量添加 2d 张量

python - 如何在HTML音频源标签中链接Django模型的属性?

java - G1GC 评论阶段花费的时间太长

Java G1 垃圾收集器占用大量内存

python - 使用 python ctypes.CDLL() 从不同目录加载 .dll 时出错

python - 调用函数时如何在 ctyps 中返回数组