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 寄存器之一中仍然存在对已分配内存的引用。对于未优化的版本,函数中可能存在额外的指令,这些指令会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 and gcc optimization 2

于 2013-10-13T09:07:17.923 回答